Merge "[am] catch IllegalArgumentException from getHarmfulAppWarning"
diff --git a/Android.bp b/Android.bp
index ed7a4813..0315c12 100644
--- a/Android.bp
+++ b/Android.bp
@@ -387,6 +387,7 @@
"av-types-aidl-java",
"tv_tuner_resource_manager_aidl_interface-java",
"soundtrigger_middleware-aidl-java",
+ "modules-utils-binary-xml",
"modules-utils-build",
"modules-utils-preconditions",
"modules-utils-statemachine",
diff --git a/apct-tests/perftests/core/src/android/util/XmlPerfTest.java b/apct-tests/perftests/core/src/android/util/XmlPerfTest.java
index e05bd2a..b83657b 100644
--- a/apct-tests/perftests/core/src/android/util/XmlPerfTest.java
+++ b/apct-tests/perftests/core/src/android/util/XmlPerfTest.java
@@ -28,6 +28,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.HexDump;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java b/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java
index 76656bd..a31184c 100644
--- a/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java
+++ b/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java
@@ -22,6 +22,9 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.FastDataInput;
+import com.android.modules.utils.FastDataOutput;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -68,7 +71,7 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
os.reset();
- final FastDataOutput out = FastDataOutput.obtainUsing4ByteSequences(os);
+ final FastDataOutput out = ArtFastDataOutput.obtain(os);
try {
doWrite(out);
out.flush();
@@ -84,7 +87,7 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
os.reset();
- final FastDataOutput out = FastDataOutput.obtainUsing3ByteSequences(os);
+ final FastDataOutput out = FastDataOutput.obtain(os);
try {
doWrite(out);
out.flush();
@@ -116,7 +119,7 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
is.reset();
- final FastDataInput in = FastDataInput.obtainUsing4ByteSequences(is);
+ final FastDataInput in = ArtFastDataInput.obtain(is);
try {
doRead(in);
} finally {
@@ -131,7 +134,7 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
is.reset();
- final FastDataInput in = FastDataInput.obtainUsing3ByteSequences(is);
+ final FastDataInput in = FastDataInput.obtain(is);
try {
doRead(in);
} finally {
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index f844ba3..cc74a52 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -30,8 +30,8 @@
import android.view.InputChannel;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.View;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -84,7 +84,7 @@
private static class TestWindow extends BaseIWindow {
final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
- final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ final int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
final Rect mOutAttachedFrame = new Rect();
@@ -106,7 +106,7 @@
long startTime = SystemClock.elapsedRealtimeNanos();
session.addToDisplay(this, mLayoutParams, View.VISIBLE,
- Display.DEFAULT_DISPLAY, mRequestedVisibilities, inputChannel,
+ Display.DEFAULT_DISPLAY, mRequestedVisibleTypes, inputChannel,
mOutInsetsState, mOutControls, mOutAttachedFrame, mOutSizeCompatScale);
final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
state.addExtraResult("add", elapsedTimeNsOfAdd);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index ff9ff34..22b0968 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -41,13 +41,13 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SystemConfigFileCommitEventLogger;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.IoThread;
import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
import com.android.server.job.controllers.JobStatus;
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index 27d00b7..ee448b5 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -33,12 +33,12 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArrayMap;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/api/current.txt b/core/api/current.txt
index 18d8fc7..eef2324 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9268,6 +9268,7 @@
field public static final int CLASSIFICATION_NOT_COMPLETE = 1; // 0x1
field public static final int CLASSIFICATION_NOT_PERFORMED = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR;
+ field public static final String EXTRA_IS_REMOTE_DEVICE = "android.content.extra.IS_REMOTE_DEVICE";
field public static final String EXTRA_IS_SENSITIVE = "android.content.extra.IS_SENSITIVE";
field public static final String MIMETYPE_TEXT_HTML = "text/html";
field public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
@@ -17593,6 +17594,7 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.Capability[]> CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_CAPABILITIES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_SCENE_MODES;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_SETTINGS_OVERRIDES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AWB_AVAILABLE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AWB_LOCK_AVAILABLE;
@@ -17947,6 +17949,8 @@
field public static final int CONTROL_SCENE_MODE_STEADYPHOTO = 11; // 0xb
field public static final int CONTROL_SCENE_MODE_SUNSET = 10; // 0xa
field public static final int CONTROL_SCENE_MODE_THEATRE = 7; // 0x7
+ field public static final int CONTROL_SETTINGS_OVERRIDE_OFF = 0; // 0x0
+ field public static final int CONTROL_SETTINGS_OVERRIDE_ZOOM = 1; // 0x1
field public static final int CONTROL_VIDEO_STABILIZATION_MODE_OFF = 0; // 0x0
field public static final int CONTROL_VIDEO_STABILIZATION_MODE_ON = 1; // 0x1
field public static final int CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION = 2; // 0x2
@@ -18145,6 +18149,7 @@
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
+ field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SETTINGS_OVERRIDE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> CONTROL_ZOOM_RATIO;
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.CaptureRequest> CREATOR;
@@ -18235,6 +18240,7 @@
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SETTINGS_OVERRIDE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> CONTROL_ZOOM_RATIO;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> DISTORTION_CORRECTION_MODE;
@@ -19491,7 +19497,7 @@
method public boolean hasSatelliteBlocklist();
method public boolean hasSatellitePvt();
method public boolean hasScheduling();
- method public boolean hasSingleShot();
+ method public boolean hasSingleShotFix();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssCapabilities> CREATOR;
}
@@ -19523,7 +19529,7 @@
method @NonNull public android.location.GnssCapabilities.Builder setHasSatelliteBlocklist(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasSatellitePvt(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasScheduling(boolean);
- method @NonNull public android.location.GnssCapabilities.Builder setHasSingleShot(boolean);
+ method @NonNull public android.location.GnssCapabilities.Builder setHasSingleShotFix(boolean);
}
public final class GnssClock implements android.os.Parcelable {
@@ -19721,7 +19727,7 @@
method public int getConstellationType(@IntRange(from=0) int);
method @FloatRange(from=0xffffffa6, to=90) public float getElevationDegrees(@IntRange(from=0) int);
method @IntRange(from=0) public int getSatelliteCount();
- method @IntRange(from=1, to=200) public int getSvid(@IntRange(from=0) int);
+ method @IntRange(from=1, to=206) public int getSvid(@IntRange(from=0) int);
method public boolean hasAlmanacData(@IntRange(from=0) int);
method public boolean hasBasebandCn0DbHz(@IntRange(from=0) int);
method public boolean hasCarrierFrequencyHz(@IntRange(from=0) int);
@@ -47425,6 +47431,7 @@
field public static final int DENSITY_420 = 420; // 0x1a4
field public static final int DENSITY_440 = 440; // 0x1b8
field public static final int DENSITY_450 = 450; // 0x1c2
+ field public static final int DENSITY_520 = 520; // 0x208
field public static final int DENSITY_560 = 560; // 0x230
field public static final int DENSITY_600 = 600; // 0x258
field public static final int DENSITY_DEFAULT = 160; // 0xa0
@@ -49939,10 +49946,13 @@
method public void clear();
method public void computeCurrentVelocity(int);
method public void computeCurrentVelocity(int, float);
+ method public float getAxisVelocity(int, int);
+ method public float getAxisVelocity(int);
method public float getXVelocity();
method public float getXVelocity(int);
method public float getYVelocity();
method public float getYVelocity(int);
+ method public boolean isAxisSupported(int);
method public static android.view.VelocityTracker obtain();
method public void recycle();
}
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index e890005..88efcce 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -301,10 +301,6 @@
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void reportNetworkInterfaceForTransports(@NonNull String, @NonNull int[]) throws java.lang.RuntimeException;
}
- public class Binder implements android.os.IBinder {
- method public final void markVintfStability();
- }
-
public class BluetoothServiceManager {
method @NonNull public android.os.BluetoothServiceManager.ServiceRegisterer getBluetoothManagerServiceRegisterer();
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7a22e37..a382ecf 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -775,11 +775,14 @@
}
public class BroadcastOptions {
+ method public void clearDeliveryGroupPolicy();
method public void clearRequireCompatChange();
+ method public int getDeliveryGroupPolicy();
method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
method public static android.app.BroadcastOptions makeBasic();
method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
+ method public void setDeliveryGroupPolicy(int);
method public void setDontSendToRestrictedApps(boolean);
method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
method public void setRequireAllOfPermissions(@Nullable String[]);
@@ -788,6 +791,8 @@
method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppAllowlist(long, int, int, @Nullable String);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long);
method public android.os.Bundle toBundle();
+ field public static final int DELIVERY_GROUP_POLICY_ALL = 0; // 0x0
+ field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1
}
public class DownloadManager {
@@ -9346,6 +9351,7 @@
public class Binder implements android.os.IBinder {
method public int handleShellCommand(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.ParcelFileDescriptor, @NonNull android.os.ParcelFileDescriptor, @NonNull String[]);
+ method public final void markVintfStability();
method public static void setProxyTransactListener(@Nullable android.os.Binder.ProxyTransactListener);
}
@@ -13392,7 +13398,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int[] getCompleteActiveSubscriptionIdList();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int);
- method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public android.os.UserHandle getUserHandle(int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public android.os.UserHandle getSubscriptionUserHandle(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int);
method public void requestEmbeddedSubscriptionInfoListRefresh();
method public void requestEmbeddedSubscriptionInfoListRefresh(int);
@@ -13402,8 +13408,8 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultVoiceSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int, boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public void setSubscriptionUserHandle(int, @Nullable android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUiccApplicationsEnabled(int, boolean);
- method @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public void setUserHandle(int, @Nullable android.os.UserHandle);
field @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS) public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED = "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED";
field @NonNull public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
field @NonNull public static final android.net.Uri CROSS_SIM_ENABLED_CONTENT_URI;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 74329a3..d6c10ae 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -81,8 +81,6 @@
import android.util.DisplayMetrics;
import android.util.Singleton;
import android.util.Size;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.window.TaskSnapshot;
import com.android.internal.app.LocalePicker;
@@ -92,6 +90,8 @@
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import java.io.FileDescriptor;
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index b803070..4b1b0a2 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -221,6 +221,12 @@
public abstract boolean isSystemReady();
/**
+ * @return {@code true} if system is using the "modern" broadcast queue,
+ * {@code false} otherwise.
+ */
+ public abstract boolean isModernQueueEnabled();
+
+ /**
* Returns package name given pid.
*
* @param pid The pid we are searching package name for.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 3f39026..d1275f6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -867,7 +867,7 @@
// when adding one of these:
// - increment _NUM_OP
- // - define an OPSTR_* constant (marked as @SystemApi)
+ // - define an OPSTR_* constant (and mark as @SystemApi if needed)
// - add row to sAppOpInfos
// - add descriptive strings to Settings/res/values/arrays.xml
// - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index cc4650a7..48638d1 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -31,6 +31,7 @@
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
+import android.os.BundleMerger;
import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerExemptionManager.TempAllowListType;
@@ -67,6 +68,7 @@
private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
private @Nullable String mDeliveryGroupKey;
+ private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
/**
* Change ID which is invalid.
@@ -218,6 +220,12 @@
"android:broadcast.deliveryGroupKey";
/**
+ * Corresponds to {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
+ */
+ private static final String KEY_DELIVERY_GROUP_EXTRAS_MERGER =
+ "android:broadcast.deliveryGroupExtrasMerger";
+
+ /**
* The list of delivery group policies which specify how multiple broadcasts belonging to
* the same delivery group has to be handled.
* @hide
@@ -225,6 +233,7 @@
@IntDef(flag = true, prefix = { "DELIVERY_GROUP_POLICY_" }, value = {
DELIVERY_GROUP_POLICY_ALL,
DELIVERY_GROUP_POLICY_MOST_RECENT,
+ DELIVERY_GROUP_POLICY_MERGED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeliveryGroupPolicy {}
@@ -235,6 +244,7 @@
*
* @hide
*/
+ @SystemApi
public static final int DELIVERY_GROUP_POLICY_ALL = 0;
/**
@@ -243,8 +253,17 @@
*
* @hide
*/
+ @SystemApi
public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1;
+ /**
+ * Delivery group policy that indicates that the extras data from the broadcasts in the
+ * delivery group need to be merged into a single broadcast and the rest can be dropped.
+ *
+ * @hide
+ */
+ public static final int DELIVERY_GROUP_POLICY_MERGED = 2;
+
public static BroadcastOptions makeBasic() {
BroadcastOptions opts = new BroadcastOptions();
return opts;
@@ -295,6 +314,8 @@
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
DELIVERY_GROUP_POLICY_ALL);
mDeliveryGroupKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
+ mDeliveryGroupExtrasMerger = opts.getParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
+ BundleMerger.class);
}
/**
@@ -724,16 +745,35 @@
*
* @hide
*/
+ @SystemApi
public void setDeliveryGroupPolicy(@DeliveryGroupPolicy int policy) {
mDeliveryGroupPolicy = policy;
}
- /** @hide */
+ /**
+ * Get the delivery group policy for this broadcast that specifies how multiple broadcasts
+ * belonging to the same delivery group has to be handled.
+ *
+ * @hide
+ */
+ @SystemApi
public @DeliveryGroupPolicy int getDeliveryGroupPolicy() {
return mDeliveryGroupPolicy;
}
/**
+ * Clears any previously set delivery group policies using
+ * {@link #setDeliveryGroupKey(String, String)} and resets the delivery group policy to
+ * the default value ({@link #DELIVERY_GROUP_POLICY_ALL}).
+ *
+ * @hide
+ */
+ @SystemApi
+ public void clearDeliveryGroupPolicy() {
+ mDeliveryGroupPolicy = DELIVERY_GROUP_POLICY_ALL;
+ }
+
+ /**
* Set namespace and key to identify the delivery group that this broadcast belongs to.
* If no namespace and key is set, then by default {@link Intent#filterEquals(Intent)} will be
* used to identify the delivery group.
@@ -754,12 +794,35 @@
}
/**
+ * Set the {@link BundleMerger} that specifies how to merge the extras data from
+ * broadcasts in a delivery group.
+ *
+ * <p>Note that this value will be ignored if the delivery group policy is not set as
+ * {@link #DELIVERY_GROUP_POLICY_MERGED}.
+ *
+ * @hide
+ */
+ public void setDeliveryGroupExtrasMerger(@NonNull BundleMerger extrasMerger) {
+ Preconditions.checkNotNull(extrasMerger);
+ mDeliveryGroupExtrasMerger = extrasMerger;
+ }
+
+ /** @hide */
+ public @Nullable BundleMerger getDeliveryGroupExtrasMerger() {
+ return mDeliveryGroupExtrasMerger;
+ }
+
+ /**
* Returns the created options as a Bundle, which can be passed to
* {@link android.content.Context#sendBroadcast(android.content.Intent)
* Context.sendBroadcast(Intent)} and related methods.
* Note that the returned Bundle is still owned by the BroadcastOptions
* object; you must not modify it, but can supply it to the sendBroadcast
* methods that take an options Bundle.
+ *
+ * @throws IllegalStateException if the broadcast option values are inconsistent. For example,
+ * if the delivery group policy is specified as "MERGED" but no
+ * extras merger is supplied.
*/
@Override
public Bundle toBundle() {
@@ -810,6 +873,15 @@
if (mDeliveryGroupKey != null) {
b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupKey);
}
+ if (mDeliveryGroupPolicy == DELIVERY_GROUP_POLICY_MERGED) {
+ if (mDeliveryGroupExtrasMerger != null) {
+ b.putParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
+ mDeliveryGroupExtrasMerger);
+ } else {
+ throw new IllegalStateException("Extras merger cannot be empty "
+ + "when delivery group policy is 'MERGED'");
+ }
+ }
return b.isEmpty() ? null : b;
}
}
diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java
index be53a62..0e2b098 100644
--- a/core/java/android/app/LocaleManager.java
+++ b/core/java/android/app/LocaleManager.java
@@ -174,7 +174,7 @@
@TestApi
public void setSystemLocales(@NonNull LocaleList locales) {
try {
- Configuration conf = ActivityManager.getService().getConfiguration();
+ Configuration conf = new Configuration();
conf.setLocales(locales);
ActivityManager.getService().updatePersistentConfiguration(conf);
} catch (RemoteException e) {
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 7215987..9615b68 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -33,12 +33,12 @@
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.json.JSONException;
import org.json.JSONObject;
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 5c29eb3..3bd86c1 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -23,10 +23,11 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.json.JSONException;
import org.json.JSONObject;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 08a6b8c..aaa3d21 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -173,6 +173,7 @@
import android.os.IUserManager;
import android.os.IncidentManager;
import android.os.PerformanceHintManager;
+import android.os.PermissionEnforcer;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.ServiceManager;
@@ -1366,6 +1367,14 @@
return new PermissionCheckerManager(ctx.getOuterContext());
}});
+ registerService(Context.PERMISSION_ENFORCER_SERVICE, PermissionEnforcer.class,
+ new CachedServiceFetcher<PermissionEnforcer>() {
+ @Override
+ public PermissionEnforcer createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ return new PermissionEnforcer(ctx.getOuterContext());
+ }});
+
registerService(Context.DYNAMIC_SYSTEM_SERVICE, DynamicSystemManager.class,
new CachedServiceFetcher<DynamicSystemManager>() {
@Override
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 5b0bd96..0f26818 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -175,6 +175,23 @@
"file_patterns": [
"(/|^)KeyguardManager.java"
]
+ },
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.app.PropertyInvalidatedCacheTests"
+ }
+ ],
+ "file_patterns": [
+ "(/|^)PropertyInvalidatedCache.java"
+ ]
}
],
"presubmit-large": [
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 41256d0..67408a4 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -37,10 +37,11 @@
import android.util.Log;
import android.util.Printer;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.java b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
index 7e95177..efa23dd 100644
--- a/core/java/android/app/admin/FactoryResetProtectionPolicy.java
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
@@ -27,8 +27,9 @@
import android.os.Parcelable;
import android.util.IndentingPrintWriter;
import android.util.Log;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/app/admin/ParcelableResource.java b/core/java/android/app/admin/ParcelableResource.java
index a297665..5b438f8 100644
--- a/core/java/android/app/admin/ParcelableResource.java
+++ b/core/java/android/app/admin/ParcelableResource.java
@@ -30,8 +30,9 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
index 63c9839..b0ea499 100644
--- a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
+++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
@@ -27,8 +27,9 @@
import android.os.Parcelable;
import android.util.IndentingPrintWriter;
import android.util.Log;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/app/admin/SystemUpdateInfo.java b/core/java/android/app/admin/SystemUpdateInfo.java
index b88bf76..9e6c91f 100644
--- a/core/java/android/app/admin/SystemUpdateInfo.java
+++ b/core/java/android/app/admin/SystemUpdateInfo.java
@@ -22,8 +22,9 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index 68ac4cc..b100eb2 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -26,8 +26,9 @@
import android.os.Parcelable;
import android.util.Log;
import android.util.Pair;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index b789b38..760c6f0 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -19,10 +19,15 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.Slog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Collections;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -38,6 +43,8 @@
* @hide
*/
public class BackupRestoreEventLogger {
+ private static final String TAG = "BackupRestoreEventLogger";
+
/**
* Max number of unique data types for which an instance of this logger can store info. Attempts
* to use more distinct data type values will be rejected.
@@ -72,6 +79,8 @@
public @interface BackupRestoreError {}
private final int mOperationType;
+ private final Map<String, DataTypeResult> mResults = new HashMap<>();
+ private final MessageDigest mHashDigest;
/**
* @param operationType type of the operation for which logging will be performed. See
@@ -81,6 +90,14 @@
*/
public BackupRestoreEventLogger(@OperationType int operationType) {
mOperationType = operationType;
+
+ MessageDigest hashDigest = null;
+ try {
+ hashDigest = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ Slog.w("Couldn't create MessageDigest for hash computation", e);
+ }
+ mHashDigest = hashDigest;
}
/**
@@ -98,7 +115,7 @@
* @return boolean, indicating whether the log has been accepted.
*/
public boolean logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
- return true;
+ return logSuccess(OperationType.BACKUP, dataType, count);
}
/**
@@ -118,7 +135,7 @@
*/
public boolean logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
- return true;
+ return logFailure(OperationType.BACKUP, dataType, count, error);
}
/**
@@ -139,7 +156,7 @@
*/
public boolean logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metaData) {
- return true;
+ return logMetaData(OperationType.BACKUP, dataType, metaData);
}
/**
@@ -159,7 +176,7 @@
* @return boolean, indicating whether the log has been accepted.
*/
public boolean logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
- return true;
+ return logSuccess(OperationType.RESTORE, dataType, count);
}
/**
@@ -181,7 +198,7 @@
*/
public boolean logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
- return true;
+ return logFailure(OperationType.RESTORE, dataType, count, error);
}
/**
@@ -204,7 +221,7 @@
*/
public boolean logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metadata) {
- return true;
+ return logMetaData(OperationType.RESTORE, dataType, metadata);
}
/**
@@ -214,7 +231,7 @@
* @hide
*/
public List<DataTypeResult> getLoggingResults() {
- return Collections.emptyList();
+ return new ArrayList<>(mResults.values());
}
/**
@@ -227,22 +244,97 @@
return mOperationType;
}
+ private boolean logSuccess(@OperationType int operationType,
+ @BackupRestoreDataType String dataType, int count) {
+ DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
+ if (dataTypeResult == null) {
+ return false;
+ }
+
+ dataTypeResult.mSuccessCount += count;
+ mResults.put(dataType, dataTypeResult);
+
+ return true;
+ }
+
+ private boolean logFailure(@OperationType int operationType,
+ @NonNull @BackupRestoreDataType String dataType, int count,
+ @Nullable @BackupRestoreError String error) {
+ DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
+ if (dataTypeResult == null) {
+ return false;
+ }
+
+ dataTypeResult.mFailCount += count;
+ if (error != null) {
+ dataTypeResult.mErrors.merge(error, count, Integer::sum);
+ }
+
+ return true;
+ }
+
+ private boolean logMetaData(@OperationType int operationType,
+ @NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) {
+ if (mHashDigest == null) {
+ return false;
+ }
+ DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
+ if (dataTypeResult == null) {
+ return false;
+ }
+
+ dataTypeResult.mMetadataHash = getMetaDataHash(metaData);
+
+ return true;
+ }
+
+ /**
+ * Get the result container for the given data type.
+ *
+ * @return {@code DataTypeResult} object corresponding to the given {@code dataType} or
+ * {@code null} if the logger can't accept logs for the given data type.
+ */
+ @Nullable
+ private DataTypeResult getDataTypeResult(@OperationType int operationType,
+ @BackupRestoreDataType String dataType) {
+ if (operationType != mOperationType) {
+ // Operation type for which we're trying to record logs doesn't match the operation
+ // type for which this logger instance was created.
+ Slog.d(TAG, "Operation type mismatch: logger created for " + mOperationType
+ + ", trying to log for " + operationType);
+ return null;
+ }
+
+ if (!mResults.containsKey(dataType)) {
+ if (mResults.keySet().size() == DATA_TYPES_ALLOWED) {
+ // This is a new data type and we're already at capacity.
+ Slog.d(TAG, "Logger is full, ignoring new data type");
+ return null;
+ }
+
+ mResults.put(dataType, new DataTypeResult(dataType));
+ }
+
+ return mResults.get(dataType);
+ }
+
+ private byte[] getMetaDataHash(String metaData) {
+ return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
+ }
+
/**
* Encapsulate logging results for a single data type.
*/
public static class DataTypeResult {
@BackupRestoreDataType
private final String mDataType;
- private final int mSuccessCount;
- private final Map<String, Integer> mErrors;
- private final byte[] mMetadataHash;
+ private int mSuccessCount;
+ private int mFailCount;
+ private final Map<String, Integer> mErrors = new HashMap<>();
+ private byte[] mMetadataHash;
- public DataTypeResult(String dataType, int successCount,
- Map<String, Integer> errors, byte[] metadataHash) {
+ public DataTypeResult(String dataType) {
mDataType = dataType;
- mSuccessCount = successCount;
- mErrors = errors;
- mMetadataHash = metadataHash;
}
@NonNull
@@ -260,6 +352,13 @@
}
/**
+ * @return number of items of the given data type that have failed to back up or restore.
+ */
+ public int getFailCount() {
+ return mFailCount;
+ }
+
+ /**
* @return mapping of {@link BackupRestoreError} to the count of items that are affected by
* the error.
*/
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index bf46611..de2ba44 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -139,21 +139,28 @@
* password or credit card number.
* <p>
* Type: boolean
- * </p>
* <p>
* This extra can be used to indicate that a ClipData contains sensitive information that
* should be redacted or hidden from view until a user takes explicit action to reveal it
* (e.g., by pasting).
- * </p>
* <p>
* Adding this extra does not change clipboard behavior or add additional security to
* the ClipData. Its purpose is essentially a rendering hint from the source application,
* asking that the data within be obfuscated or redacted, unless the user has taken action
* to make it visible.
- * </p>
*/
public static final String EXTRA_IS_SENSITIVE = "android.content.extra.IS_SENSITIVE";
+ /** Indicates that a ClipData's source is a remote device.
+ * <p>
+ * Type: boolean
+ * <p>
+ * This extra can be used to indicate that a ClipData comes from a separate device rather
+ * than being local. It is a rendering hint that can be used to take different behavior
+ * based on the source device of copied data.
+ */
+ public static final String EXTRA_IS_REMOTE_DEVICE = "android.content.extra.IS_REMOTE_DEVICE";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value =
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index 5f85984..f12e971 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -314,17 +314,14 @@
*/
@Override
public boolean equals(@Nullable Object obj) {
- try {
- if (obj != null) {
- ComponentName other = (ComponentName)obj;
- // Note: no null checks, because mPackage and mClass can
- // never be null.
- return mPackage.equals(other.mPackage)
- && mClass.equals(other.mClass);
- }
- } catch (ClassCastException e) {
+ if (obj instanceof ComponentName) {
+ ComponentName other = (ComponentName) obj;
+ // mPackage and mClass can never be null.
+ return mPackage.equals(other.mPackage)
+ && mClass.equals(other.mClass);
+ } else {
+ return false;
}
- return false;
}
@Override
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d65210b..cbc1789 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5142,6 +5142,14 @@
public static final String PERMISSION_CHECKER_SERVICE = "permission_checker";
/**
+ * Official published name of the (internal) permission enforcer service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String PERMISSION_ENFORCER_SERVICE = "permission_enforcer";
+
+ /**
* Use with {@link #getSystemService(String) to retrieve an
* {@link android.apphibernation.AppHibernationManager}} for
* communicating with the hibernation service.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 43fa617..f2ebec6 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -49,6 +49,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.BundleMerger;
import android.os.IBinder;
import android.os.IncidentManager;
import android.os.Parcel;
@@ -11072,6 +11073,20 @@
}
/**
+ * Merge the extras data in this intent with that of other supplied intent using the
+ * strategy specified using {@code extrasMerger}.
+ *
+ * <p> Note the extras data in this intent is treated as the {@code first} param
+ * and the extras data in {@code other} intent is treated as the {@code last} param
+ * when using the passed in {@link BundleMerger} object.
+ *
+ * @hide
+ */
+ public void mergeExtras(@NonNull Intent other, @NonNull BundleMerger extrasMerger) {
+ mExtras = extrasMerger.merge(mExtras, other.mExtras);
+ }
+
+ /**
* Wrapper class holding an Intent and implementing comparisons on it for
* the purpose of filtering. The class implements its
* {@link #equals equals()} and {@link #hashCode hashCode()} methods as
diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java
index 495f94f..bf9dc8e 100644
--- a/core/java/android/content/SyncAdaptersCache.java
+++ b/core/java/android/content/SyncAdaptersCache.java
@@ -25,10 +25,10 @@
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java
index 56b8bd8..2b40fdf 100644
--- a/core/java/android/content/pm/IntentFilterVerificationInfo.java
+++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java
@@ -28,10 +28,10 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 823c142..aa86af9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2205,6 +2205,13 @@
*/
public static final int INSTALL_ACTIVATION_FAILED = -128;
+ /**
+ * Installation failed return code: requesting user pre-approval is currently unavailable.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE = -129;
+
/** @hide */
@IntDef(flag = true, prefix = { "DELETE_" }, value = {
DELETE_KEEP_DATA,
@@ -9643,6 +9650,7 @@
case INSTALL_FAILED_NO_MATCHING_ABIS: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
case INSTALL_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED;
case INSTALL_FAILED_MISSING_SPLIT: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE: return PackageInstaller.STATUS_FAILURE_BLOCKED;
default: return PackageInstaller.STATUS_FAILURE;
}
}
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 78984bd..104527e 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -36,14 +36,14 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index d94b0d8..b049880 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -21,9 +21,9 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.ArrayUtils;
+import com.android.modules.utils.TypedXmlSerializer;
import java.io.ByteArrayInputStream;
import java.io.IOException;
diff --git a/core/java/android/content/pm/SuspendDialogInfo.java b/core/java/android/content/pm/SuspendDialogInfo.java
index 23945ee..8786f7c 100644
--- a/core/java/android/content/pm/SuspendDialogInfo.java
+++ b/core/java/android/content/pm/SuspendDialogInfo.java
@@ -29,11 +29,11 @@
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import java.io.IOException;
import java.lang.annotation.Retention;
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index b345d50..645a1ac 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -22,10 +22,10 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/content/pm/XmlSerializerAndParser.java b/core/java/android/content/pm/XmlSerializerAndParser.java
index 51cd6ca..d748aa1 100644
--- a/core/java/android/content/pm/XmlSerializerAndParser.java
+++ b/core/java/android/content/pm/XmlSerializerAndParser.java
@@ -17,10 +17,10 @@
package android.content.pm;
import android.compat.annotation.UnsupportedAppUsage;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/credentials/Credential.java b/core/java/android/credentials/Credential.java
index a247d16..fed2592 100644
--- a/core/java/android/credentials/Credential.java
+++ b/core/java/android/credentials/Credential.java
@@ -32,6 +32,13 @@
public final class Credential implements Parcelable {
/**
+ * The type value for password credential related operations.
+ *
+ * @hide
+ */
+ @NonNull public static final String TYPE_PASSWORD = "android.credentials.TYPE_PASSWORD";
+
+ /**
* The credential type.
*/
@NonNull
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
index b9ee72d..33427d3 100644
--- a/core/java/android/credentials/ui/Entry.java
+++ b/core/java/android/credentials/ui/Entry.java
@@ -82,32 +82,46 @@
public static final String EXTRA_ENTRY_AUTHENTICATION_ACTION =
"android.credentials.ui.extra.ENTRY_AUTHENTICATION_ACTION";
- // TODO: change to string key + string subkey.
- private final int mId;
+ @NonNull private final String mKey;
+ @NonNull private final String mSubkey;
@NonNull
private final Slice mSlice;
protected Entry(@NonNull Parcel in) {
- int entryId = in.readInt();
+ String key = in.readString8();
+ String subkey = in.readString8();
Slice slice = Slice.CREATOR.createFromParcel(in);
- mId = entryId;
+ mKey = key;
+ AnnotationValidations.validate(NonNull.class, null, mKey);
+ mSubkey = subkey;
+ AnnotationValidations.validate(NonNull.class, null, mSubkey);
mSlice = slice;
AnnotationValidations.validate(NonNull.class, null, mSlice);
}
- public Entry(int id, @NonNull Slice slice) {
- mId = id;
+ public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice) {
+ mKey = key;
+ mSubkey = subkey;
mSlice = slice;
}
/**
- * Returns the id of this entry that's unique within the context of the CredentialManager
+ * Returns the identifier of this entry that's unique within the context of the CredentialManager
* request.
*/
- public int getEntryId() {
- return mId;
+ @NonNull
+ public String getKey() {
+ return mKey;
+ }
+
+ /**
+ * Returns the sub-identifier of this entry that's unique within the context of the {@code key}.
+ */
+ @NonNull
+ public String getSubkey() {
+ return mSubkey;
}
/**
@@ -120,7 +134,8 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mId);
+ dest.writeString8(mKey);
+ dest.writeString8(mSubkey);
mSlice.writeToParcel(dest, flags);
}
diff --git a/core/java/android/credentials/ui/UserSelectionDialogResult.java b/core/java/android/credentials/ui/UserSelectionDialogResult.java
index eb3a4a8..6025d78 100644
--- a/core/java/android/credentials/ui/UserSelectionDialogResult.java
+++ b/core/java/android/credentials/ui/UserSelectionDialogResult.java
@@ -54,18 +54,17 @@
private static final String EXTRA_USER_SELECTION_RESULT =
"android.credentials.ui.extra.USER_SELECTION_RESULT";
- @NonNull
- private final String mProviderId;
-
- // TODO: consider switching to string or other types, depending on the service implementation.
- private final int mEntryId;
+ @NonNull private final String mProviderId;
+ @NonNull private final String mEntryKey;
+ @NonNull private final String mEntrySubkey;
public UserSelectionDialogResult(
@NonNull IBinder requestToken, @NonNull String providerId,
- int entryId) {
+ @NonNull String entryKey, @NonNull String entrySubkey) {
super(requestToken);
mProviderId = providerId;
- mEntryId = entryId;
+ mEntryKey = entryKey;
+ mEntrySubkey = entrySubkey;
}
/** Returns provider package name whose entry was selected by the user. */
@@ -74,26 +73,38 @@
return mProviderId;
}
- /** Returns the id of the visual entry that the user selected. */
- public int getEntryId() {
- return mEntryId;
+ /** Returns the key of the visual entry that the user selected. */
+ @NonNull
+ public String getEntryKey() {
+ return mEntryKey;
+ }
+
+ /** Returns the subkey of the visual entry that the user selected. */
+ @NonNull
+ public String getEntrySubkey() {
+ return mEntrySubkey;
}
protected UserSelectionDialogResult(@NonNull Parcel in) {
super(in);
String providerId = in.readString8();
- int entryId = in.readInt();
+ String entryKey = in.readString8();
+ String entrySubkey = in.readString8();
mProviderId = providerId;
AnnotationValidations.validate(NonNull.class, null, mProviderId);
- mEntryId = entryId;
+ mEntryKey = entryKey;
+ AnnotationValidations.validate(NonNull.class, null, mEntryKey);
+ mEntrySubkey = entrySubkey;
+ AnnotationValidations.validate(NonNull.class, null, mEntrySubkey);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString8(mProviderId);
- dest.writeInt(mEntryId);
+ dest.writeString8(mEntryKey);
+ dest.writeString8(mEntrySubkey);
}
@Override
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index dae09f0..510985c 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -24,7 +24,8 @@
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.text.FontConfig;
-import android.util.TypedXmlSerializer;
+
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index f634726..d3cb59d 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1286,6 +1286,30 @@
new Key<android.hardware.camera2.params.HighSpeedVideoConfiguration[]>("android.control.availableHighSpeedVideoConfigurationsMaximumResolution", android.hardware.camera2.params.HighSpeedVideoConfiguration[].class);
/**
+ * <p>List of available settings overrides supported by the camera device that can
+ * be used to speed up certain controls.</p>
+ * <p>When not all controls within a CaptureRequest are required to take effect
+ * at the same time on the outputs, the camera device may apply certain request keys sooner
+ * to improve latency. This list contains such supported settings overrides. Each settings
+ * override corresponds to a set of CaptureRequest keys that can be sped up when applying.</p>
+ * <p>A supported settings override can be passed in via
+ * {@link android.hardware.camera2.CaptureRequest#CONTROL_SETTINGS_OVERRIDE }, and the
+ * CaptureRequest keys corresponding to the override are applied as soon as possible, not
+ * bound by per-frame synchronization. See {@link CaptureRequest#CONTROL_SETTINGS_OVERRIDE android.control.settingsOverride} for the
+ * CaptureRequest keys for each override.</p>
+ * <p>OFF is always included in this list.</p>
+ * <p><b>Range of valid values:</b><br>
+ * Any value listed in {@link CaptureRequest#CONTROL_SETTINGS_OVERRIDE android.control.settingsOverride}</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_SETTINGS_OVERRIDE
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<int[]> CONTROL_AVAILABLE_SETTINGS_OVERRIDES =
+ new Key<int[]>("android.control.availableSettingsOverrides", int[].class);
+
+ /**
* <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
* device.</p>
* <p>Full-capability camera devices must always support OFF; camera devices that support
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 1e1d443..545aa8f 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3201,6 +3201,49 @@
public static final int CONTROL_EXTENDED_SCENE_MODE_VENDOR_START = 0x40;
//
+ // Enumeration values for CaptureRequest#CONTROL_SETTINGS_OVERRIDE
+ //
+
+ /**
+ * <p>No keys are applied sooner than the other keys when applying CaptureRequest
+ * settings to the camera device. This is the default value.</p>
+ * @see CaptureRequest#CONTROL_SETTINGS_OVERRIDE
+ */
+ public static final int CONTROL_SETTINGS_OVERRIDE_OFF = 0;
+
+ /**
+ * <p>Zoom related keys are applied sooner than the other keys in the CaptureRequest. The
+ * zoom related keys are:</p>
+ * <ul>
+ * <li>{@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}</li>
+ * <li>{@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}</li>
+ * <li>{@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}</li>
+ * <li>{@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}</li>
+ * <li>{@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}</li>
+ * </ul>
+ * <p>Even though {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}, {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions},
+ * and {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions} are not directly zoom related, applications
+ * typically scale these regions together with {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to have a
+ * consistent mapping within the current field of view. In this aspect, they are
+ * related to {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} and {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_REGIONS
+ * @see CaptureRequest#CONTROL_AF_REGIONS
+ * @see CaptureRequest#CONTROL_AWB_REGIONS
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CaptureRequest#CONTROL_SETTINGS_OVERRIDE
+ */
+ public static final int CONTROL_SETTINGS_OVERRIDE_ZOOM = 1;
+
+ /**
+ * <p>Vendor defined settingsOverride. These depend on vendor implementation.</p>
+ * @see CaptureRequest#CONTROL_SETTINGS_OVERRIDE
+ * @hide
+ */
+ public static final int CONTROL_SETTINGS_OVERRIDE_VENDOR_START = 0x4000;
+
+ //
// Enumeration values for CaptureRequest#EDGE_MODE
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index c5cf0f6..407ea07 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2429,6 +2429,90 @@
new Key<Boolean>("android.control.awbRegionsSet", boolean.class);
/**
+ * <p>The desired CaptureRequest settings override with which certain keys are
+ * applied earlier so that they can take effect sooner.</p>
+ * <p>There are some CaptureRequest keys which can be applied earlier than others
+ * when controls within a CaptureRequest aren't required to take effect at the same time.
+ * One such example is zoom. Zoom can be applied at a later stage of the camera pipeline.
+ * As soon as the camera device receives the CaptureRequest, it can apply the requested
+ * zoom value onto an earlier request that's already in the pipeline, thus improves zoom
+ * latency.</p>
+ * <p>This key's value in the capture result reflects whether the controls for this capture
+ * are overridden "by" a newer request. This means that if a capture request turns on
+ * settings override, the capture result of an earlier request will contain the key value
+ * of ZOOM. On the other hand, if a capture request has settings override turned on,
+ * but all newer requests have it turned off, the key's value in the capture result will
+ * be OFF because this capture isn't overridden by a newer capture. In the two examples
+ * below, the capture results columns illustrate the settingsOverride values in different
+ * scenarios.</p>
+ * <p>Assuming the zoom settings override can speed up by 1 frame, below example illustrates
+ * the speed-up at the start of capture session:</p>
+ * <pre><code>Camera session created
+ * Request 1 (zoom=1.0x, override=ZOOM) ->
+ * Request 2 (zoom=1.2x, override=ZOOM) ->
+ * Request 3 (zoom=1.4x, override=ZOOM) -> Result 1 (zoom=1.2x, override=ZOOM)
+ * Request 4 (zoom=1.6x, override=ZOOM) -> Result 2 (zoom=1.4x, override=ZOOM)
+ * Request 5 (zoom=1.8x, override=ZOOM) -> Result 3 (zoom=1.6x, override=ZOOM)
+ * -> Result 4 (zoom=1.8x, override=ZOOM)
+ * -> Result 5 (zoom=1.8x, override=OFF)
+ * </code></pre>
+ * <p>The application can turn on settings override and use zoom as normal. The example
+ * shows that the later zoom values (1.2x, 1.4x, 1.6x, and 1.8x) overwrite the zoom
+ * values (1.0x, 1.2x, 1.4x, and 1.8x) of earlier requests (#1, #2, #3, and #4).</p>
+ * <p>The application must make sure the settings override doesn't interfere with user
+ * journeys requiring simultaneous application of all controls in CaptureRequest on the
+ * requested output targets. For example, if the application takes a still capture using
+ * CameraCaptureSession#capture, and the repeating request immediately sets a different
+ * zoom value using override, the inflight still capture could have its zoom value
+ * overwritten unexpectedly.</p>
+ * <p>So the application is strongly recommended to turn off settingsOverride when taking
+ * still/burst captures, and turn it back on when there is only repeating viewfinder
+ * request and no inflight still/burst captures.</p>
+ * <p>Below is the example demonstrating the transitions in and out of the
+ * settings override:</p>
+ * <pre><code>Request 1 (zoom=1.0x, override=OFF)
+ * Request 2 (zoom=1.2x, override=OFF)
+ * Request 3 (zoom=1.4x, override=ZOOM) -> Result 1 (zoom=1.0x, override=OFF)
+ * Request 4 (zoom=1.6x, override=ZOOM) -> Result 2 (zoom=1.4x, override=ZOOM)
+ * Request 5 (zoom=1.8x, override=OFF) -> Result 3 (zoom=1.6x, override=ZOOM)
+ * -> Result 4 (zoom=1.6x, override=OFF)
+ * -> Result 5 (zoom=1.8x, override=OFF)
+ * </code></pre>
+ * <p>This example shows that:</p>
+ * <ul>
+ * <li>The application "ramps in" settings override by setting the control to ZOOM.
+ * In the example, request #3 enables zoom settings override. Because the camera device
+ * can speed up applying zoom by 1 frame, the outputs of request #2 has 1.4x zoom, the
+ * value specified in request #3.</li>
+ * <li>The application "ramps out" of settings override by setting the control to OFF. In
+ * the example, request #5 changes the override to OFF. Because request #4's zoom
+ * takes effect in result #3, result #4's zoom remains the same until new value takes
+ * effect in result #5.</li>
+ * </ul>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_SETTINGS_OVERRIDE_OFF OFF}</li>
+ * <li>{@link #CONTROL_SETTINGS_OVERRIDE_ZOOM ZOOM}</li>
+ * </ul>
+ *
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES android.control.availableSettingsOverrides}</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see #CONTROL_SETTINGS_OVERRIDE_OFF
+ * @see #CONTROL_SETTINGS_OVERRIDE_ZOOM
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> CONTROL_SETTINGS_OVERRIDE =
+ new Key<Integer>("android.control.settingsOverride", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 1a15596..c4f0cab 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2633,6 +2633,90 @@
new Key<Float>("android.control.zoomRatio", float.class);
/**
+ * <p>The desired CaptureRequest settings override with which certain keys are
+ * applied earlier so that they can take effect sooner.</p>
+ * <p>There are some CaptureRequest keys which can be applied earlier than others
+ * when controls within a CaptureRequest aren't required to take effect at the same time.
+ * One such example is zoom. Zoom can be applied at a later stage of the camera pipeline.
+ * As soon as the camera device receives the CaptureRequest, it can apply the requested
+ * zoom value onto an earlier request that's already in the pipeline, thus improves zoom
+ * latency.</p>
+ * <p>This key's value in the capture result reflects whether the controls for this capture
+ * are overridden "by" a newer request. This means that if a capture request turns on
+ * settings override, the capture result of an earlier request will contain the key value
+ * of ZOOM. On the other hand, if a capture request has settings override turned on,
+ * but all newer requests have it turned off, the key's value in the capture result will
+ * be OFF because this capture isn't overridden by a newer capture. In the two examples
+ * below, the capture results columns illustrate the settingsOverride values in different
+ * scenarios.</p>
+ * <p>Assuming the zoom settings override can speed up by 1 frame, below example illustrates
+ * the speed-up at the start of capture session:</p>
+ * <pre><code>Camera session created
+ * Request 1 (zoom=1.0x, override=ZOOM) ->
+ * Request 2 (zoom=1.2x, override=ZOOM) ->
+ * Request 3 (zoom=1.4x, override=ZOOM) -> Result 1 (zoom=1.2x, override=ZOOM)
+ * Request 4 (zoom=1.6x, override=ZOOM) -> Result 2 (zoom=1.4x, override=ZOOM)
+ * Request 5 (zoom=1.8x, override=ZOOM) -> Result 3 (zoom=1.6x, override=ZOOM)
+ * -> Result 4 (zoom=1.8x, override=ZOOM)
+ * -> Result 5 (zoom=1.8x, override=OFF)
+ * </code></pre>
+ * <p>The application can turn on settings override and use zoom as normal. The example
+ * shows that the later zoom values (1.2x, 1.4x, 1.6x, and 1.8x) overwrite the zoom
+ * values (1.0x, 1.2x, 1.4x, and 1.8x) of earlier requests (#1, #2, #3, and #4).</p>
+ * <p>The application must make sure the settings override doesn't interfere with user
+ * journeys requiring simultaneous application of all controls in CaptureRequest on the
+ * requested output targets. For example, if the application takes a still capture using
+ * CameraCaptureSession#capture, and the repeating request immediately sets a different
+ * zoom value using override, the inflight still capture could have its zoom value
+ * overwritten unexpectedly.</p>
+ * <p>So the application is strongly recommended to turn off settingsOverride when taking
+ * still/burst captures, and turn it back on when there is only repeating viewfinder
+ * request and no inflight still/burst captures.</p>
+ * <p>Below is the example demonstrating the transitions in and out of the
+ * settings override:</p>
+ * <pre><code>Request 1 (zoom=1.0x, override=OFF)
+ * Request 2 (zoom=1.2x, override=OFF)
+ * Request 3 (zoom=1.4x, override=ZOOM) -> Result 1 (zoom=1.0x, override=OFF)
+ * Request 4 (zoom=1.6x, override=ZOOM) -> Result 2 (zoom=1.4x, override=ZOOM)
+ * Request 5 (zoom=1.8x, override=OFF) -> Result 3 (zoom=1.6x, override=ZOOM)
+ * -> Result 4 (zoom=1.6x, override=OFF)
+ * -> Result 5 (zoom=1.8x, override=OFF)
+ * </code></pre>
+ * <p>This example shows that:</p>
+ * <ul>
+ * <li>The application "ramps in" settings override by setting the control to ZOOM.
+ * In the example, request #3 enables zoom settings override. Because the camera device
+ * can speed up applying zoom by 1 frame, the outputs of request #2 has 1.4x zoom, the
+ * value specified in request #3.</li>
+ * <li>The application "ramps out" of settings override by setting the control to OFF. In
+ * the example, request #5 changes the override to OFF. Because request #4's zoom
+ * takes effect in result #3, result #4's zoom remains the same until new value takes
+ * effect in result #5.</li>
+ * </ul>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_SETTINGS_OVERRIDE_OFF OFF}</li>
+ * <li>{@link #CONTROL_SETTINGS_OVERRIDE_ZOOM ZOOM}</li>
+ * </ul>
+ *
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES android.control.availableSettingsOverrides}</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see #CONTROL_SETTINGS_OVERRIDE_OFF
+ * @see #CONTROL_SETTINGS_OVERRIDE_ZOOM
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> CONTROL_SETTINGS_OVERRIDE =
+ new Key<Integer>("android.control.settingsOverride", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
index 97ce183..84ca2b6 100644
--- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
@@ -24,4 +24,5 @@
List<CameraOutputConfig> outputConfigs;
CameraMetadataNative sessionParameter;
int sessionTemplateId;
+ int sessionType;
}
diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
index a8a7866e..3a0c3a5 100644
--- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
@@ -31,6 +31,7 @@
@nullable CaptureStageImpl onPresetSession();
@nullable CaptureStageImpl onEnableSession();
@nullable CaptureStageImpl onDisableSession();
+ int getSessionType();
boolean isExtensionAvailable(in String cameraId, in CameraMetadataNative chars);
void init(in String cameraId, in CameraMetadataNative chars);
diff --git a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
index 2d67344..01046d0 100644
--- a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
@@ -34,6 +34,7 @@
void init(in String cameraId, in CameraMetadataNative chars);
boolean isExtensionAvailable(in String cameraId, in CameraMetadataNative chars);
@nullable CaptureStageImpl getCaptureStage();
+ int getSessionType();
const int PROCESSOR_TYPE_REQUEST_UPDATE_ONLY = 0;
const int PROCESSOR_TYPE_IMAGE_PROCESSOR = 1;
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index c8dc2d0..01c1ef4 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -243,9 +243,16 @@
mCameraConfigMap.put(cameraOutput.getSurface(), output);
}
- SessionConfiguration sessionConfiguration = new SessionConfiguration(
- SessionConfiguration.SESSION_REGULAR, outputList,
- new CameraExtensionUtils.HandlerExecutor(mHandler), new SessionStateHandler());
+ int sessionType = SessionConfiguration.SESSION_REGULAR;
+ if (sessionConfig.sessionType != -1 &&
+ (sessionConfig.sessionType != SessionConfiguration.SESSION_HIGH_SPEED)) {
+ sessionType = sessionConfig.sessionType;
+ Log.v(TAG, "Using session type: " + sessionType);
+ }
+
+ SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType,
+ outputList, new CameraExtensionUtils.HandlerExecutor(mHandler),
+ new SessionStateHandler());
if ((sessionConfig.sessionParameter != null) &&
(!sessionConfig.sessionParameter.isEmpty())) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 41822e7..1f9f3b8 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -415,6 +415,18 @@
"Session already initialized");
return;
}
+ int previewSessionType = mPreviewExtender.getSessionType();
+ int imageSessionType = mImageExtender.getSessionType();
+ if (previewSessionType != imageSessionType) {
+ throw new IllegalStateException("Preview extender session type: " + previewSessionType +
+ "and image extender session type: " + imageSessionType + " mismatch!");
+ }
+ int sessionType = SessionConfiguration.SESSION_REGULAR;
+ if ((previewSessionType != -1) &&
+ (previewSessionType != SessionConfiguration.SESSION_HIGH_SPEED)) {
+ sessionType = previewSessionType;
+ Log.v(TAG, "Using session type: " + sessionType);
+ }
ArrayList<CaptureStageImpl> sessionParamsList = new ArrayList<>();
ArrayList<OutputConfiguration> outputList = new ArrayList<>();
@@ -432,7 +444,7 @@
}
SessionConfiguration sessionConfig = new SessionConfiguration(
- SessionConfiguration.SESSION_REGULAR,
+ sessionType,
outputList,
new CameraExtensionUtils.HandlerExecutor(mHandler),
new SessionStateHandler());
diff --git a/core/java/android/hardware/display/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java
index 366734e..007b37f 100644
--- a/core/java/android/hardware/display/BrightnessConfiguration.java
+++ b/core/java/android/hardware/display/BrightnessConfiguration.java
@@ -24,11 +24,11 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Pair;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/hardware/display/BrightnessCorrection.java b/core/java/android/hardware/display/BrightnessCorrection.java
index 2919ec3..5ff08ba 100644
--- a/core/java/android/hardware/display/BrightnessCorrection.java
+++ b/core/java/android/hardware/display/BrightnessCorrection.java
@@ -23,10 +23,10 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.MathUtils;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
index 76ee097..f8e2558 100644
--- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
+++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
@@ -37,7 +37,7 @@
import android.util.AtomicFile;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FastDataInput;
+import com.android.internal.util.ArtFastDataInput;
import libcore.io.IoUtils;
@@ -254,7 +254,7 @@
private static void readPlatformCollection(@NonNull NetworkStatsCollection.Builder builder,
@NonNull File file) throws IOException {
final FileInputStream is = new FileInputStream(file);
- final FastDataInput dataIn = new FastDataInput(is, BUFFER_SIZE);
+ final ArtFastDataInput dataIn = new ArtFastDataInput(is, BUFFER_SIZE);
try {
readPlatformCollection(builder, dataIn);
} finally {
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
index 068df22..7a153ef 100644
--- a/core/java/android/os/AggregateBatteryConsumer.java
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -17,10 +17,11 @@
package android.os;
import android.annotation.NonNull;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 0c5f778..e2c52ce 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -22,12 +22,12 @@
import android.database.CursorWindow;
import android.util.Range;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index d3a6323..2643558 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -562,7 +562,7 @@
*
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
public final native void markVintfStability();
/**
diff --git a/core/java/android/os/BundleMerger.java b/core/java/android/os/BundleMerger.java
new file mode 100644
index 0000000..51bd4ea
--- /dev/null
+++ b/core/java/android/os/BundleMerger.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.function.BinaryOperator;
+
+/**
+ * Configured rules for merging two {@link Bundle} instances.
+ * <p>
+ * By default, values from both {@link Bundle} instances are blended together on
+ * a key-wise basis, and conflicting value definitions for a key are dropped.
+ * <p>
+ * Nuanced strategies for handling conflicting value definitions can be applied
+ * using {@link #setMergeStrategy(String, int)} and
+ * {@link #setDefaultMergeStrategy(int)}.
+ * <p>
+ * When conflicting values have <em>inconsistent</em> data types (such as trying
+ * to merge a {@link String} and a {@link Integer}), both conflicting values are
+ * rejected and the key becomes undefined, regardless of the requested strategy.
+ *
+ * @hide
+ */
+public class BundleMerger implements Parcelable {
+ private static final String TAG = "BundleMerger";
+
+ private @Strategy int mDefaultStrategy = STRATEGY_REJECT;
+
+ private final ArrayMap<String, Integer> mStrategies = new ArrayMap<>();
+
+ /**
+ * Merge strategy that rejects both conflicting values.
+ */
+ public static final int STRATEGY_REJECT = 0;
+
+ /**
+ * Merge strategy that selects the first of conflicting values.
+ */
+ public static final int STRATEGY_FIRST = 1;
+
+ /**
+ * Merge strategy that selects the last of conflicting values.
+ */
+ public static final int STRATEGY_LAST = 2;
+
+ /**
+ * Merge strategy that selects the "minimum" of conflicting values which are
+ * {@link Comparable} with each other.
+ */
+ public static final int STRATEGY_COMPARABLE_MIN = 3;
+
+ /**
+ * Merge strategy that selects the "maximum" of conflicting values which are
+ * {@link Comparable} with each other.
+ */
+ public static final int STRATEGY_COMPARABLE_MAX = 4;
+
+ /**
+ * Merge strategy that numerically adds both conflicting values.
+ */
+ public static final int STRATEGY_NUMBER_ADD = 5;
+
+ /**
+ * Merge strategy that numerically increments the first conflicting value by
+ * {@code 1} and ignores the last conflicting value.
+ */
+ public static final int STRATEGY_NUMBER_INCREMENT_FIRST = 6;
+
+ /**
+ * Merge strategy that combines conflicting values using a boolean "and"
+ * operation.
+ */
+ public static final int STRATEGY_BOOLEAN_AND = 7;
+
+ /**
+ * Merge strategy that combines conflicting values using a boolean "or"
+ * operation.
+ */
+ public static final int STRATEGY_BOOLEAN_OR = 8;
+
+ /**
+ * Merge strategy that combines two conflicting array values by appending
+ * the last array after the first array.
+ */
+ public static final int STRATEGY_ARRAY_APPEND = 9;
+
+ /**
+ * Merge strategy that combines two conflicting {@link ArrayList} values by
+ * appending the last {@link ArrayList} after the first {@link ArrayList}.
+ */
+ public static final int STRATEGY_ARRAY_LIST_APPEND = 10;
+
+ @IntDef(flag = false, prefix = { "STRATEGY_" }, value = {
+ STRATEGY_REJECT,
+ STRATEGY_FIRST,
+ STRATEGY_LAST,
+ STRATEGY_COMPARABLE_MIN,
+ STRATEGY_COMPARABLE_MAX,
+ STRATEGY_NUMBER_ADD,
+ STRATEGY_NUMBER_INCREMENT_FIRST,
+ STRATEGY_BOOLEAN_AND,
+ STRATEGY_BOOLEAN_OR,
+ STRATEGY_ARRAY_APPEND,
+ STRATEGY_ARRAY_LIST_APPEND,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Strategy {}
+
+ /**
+ * Create a empty set of rules for merging two {@link Bundle} instances.
+ */
+ public BundleMerger() {
+ }
+
+ private BundleMerger(@NonNull Parcel in) {
+ mDefaultStrategy = in.readInt();
+ final int N = in.readInt();
+ for (int i = 0; i < N; i++) {
+ mStrategies.put(in.readString(), in.readInt());
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mDefaultStrategy);
+ final int N = mStrategies.size();
+ out.writeInt(N);
+ for (int i = 0; i < N; i++) {
+ out.writeString(mStrategies.keyAt(i));
+ out.writeInt(mStrategies.valueAt(i));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Configure the default merge strategy to be used when there isn't a
+ * more-specific strategy defined for a particular key via
+ * {@link #setMergeStrategy(String, int)}.
+ */
+ public void setDefaultMergeStrategy(@Strategy int strategy) {
+ mDefaultStrategy = strategy;
+ }
+
+ /**
+ * Configure the merge strategy to be used for the given key.
+ * <p>
+ * Subsequent calls for the same key will overwrite any previously
+ * configured strategy.
+ */
+ public void setMergeStrategy(@NonNull String key, @Strategy int strategy) {
+ mStrategies.put(key, strategy);
+ }
+
+ /**
+ * Return the merge strategy to be used for the given key, as defined by
+ * {@link #setMergeStrategy(String, int)}.
+ * <p>
+ * If no specific strategy has been configured for the given key, this
+ * returns {@link #setDefaultMergeStrategy(int)}.
+ */
+ public @Strategy int getMergeStrategy(@NonNull String key) {
+ return (int) mStrategies.getOrDefault(key, mDefaultStrategy);
+ }
+
+ /**
+ * Return a {@link BinaryOperator} which applies the strategies configured
+ * in this object to merge the two given {@link Bundle} arguments.
+ */
+ public BinaryOperator<Bundle> asBinaryOperator() {
+ return this::merge;
+ }
+
+ /**
+ * Apply the strategies configured in this object to merge the two given
+ * {@link Bundle} arguments.
+ *
+ * @return the merged {@link Bundle} result. If one argument is {@code null}
+ * it will return the other argument. If both arguments are null it
+ * will return {@code null}.
+ */
+ @SuppressWarnings("deprecation")
+ public @Nullable Bundle merge(@Nullable Bundle first, @Nullable Bundle last) {
+ if (first == null && last == null) {
+ return null;
+ }
+ if (first == null) {
+ first = Bundle.EMPTY;
+ }
+ if (last == null) {
+ last = Bundle.EMPTY;
+ }
+
+ // Start by bulk-copying all values without attempting to unpack any
+ // custom parcelables; we'll circle back to handle conflicts below
+ final Bundle res = new Bundle();
+ res.putAll(first);
+ res.putAll(last);
+
+ final ArraySet<String> conflictingKeys = new ArraySet<>();
+ conflictingKeys.addAll(first.keySet());
+ conflictingKeys.retainAll(last.keySet());
+ for (int i = 0; i < conflictingKeys.size(); i++) {
+ final String key = conflictingKeys.valueAt(i);
+ final int strategy = getMergeStrategy(key);
+ final Object firstValue = first.get(key);
+ final Object lastValue = last.get(key);
+ try {
+ res.putObject(key, merge(strategy, firstValue, lastValue));
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to merge key " + key + " with " + firstValue + " and "
+ + lastValue + " using strategy " + strategy, e);
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Merge the two given values. If only one of the values is defined, it
+ * always wins, otherwise the given strategy is applied.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static @Nullable Object merge(@Strategy int strategy,
+ @Nullable Object first, @Nullable Object last) {
+ if (first == null) return last;
+ if (last == null) return first;
+
+ if (first.getClass() != last.getClass()) {
+ throw new IllegalArgumentException("Merging requires consistent classes; first "
+ + first.getClass() + " last " + last.getClass());
+ }
+
+ switch (strategy) {
+ case STRATEGY_REJECT:
+ // Only actually reject when the values are different
+ if (Objects.deepEquals(first, last)) {
+ return first;
+ } else {
+ return null;
+ }
+ case STRATEGY_FIRST:
+ return first;
+ case STRATEGY_LAST:
+ return last;
+ case STRATEGY_COMPARABLE_MIN:
+ return comparableMin(first, last);
+ case STRATEGY_COMPARABLE_MAX:
+ return comparableMax(first, last);
+ case STRATEGY_NUMBER_ADD:
+ return numberAdd(first, last);
+ case STRATEGY_NUMBER_INCREMENT_FIRST:
+ return numberIncrementFirst(first, last);
+ case STRATEGY_BOOLEAN_AND:
+ return booleanAnd(first, last);
+ case STRATEGY_BOOLEAN_OR:
+ return booleanOr(first, last);
+ case STRATEGY_ARRAY_APPEND:
+ return arrayAppend(first, last);
+ case STRATEGY_ARRAY_LIST_APPEND:
+ return arrayListAppend(first, last);
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static @NonNull Object comparableMin(@NonNull Object first, @NonNull Object last) {
+ return ((Comparable<Object>) first).compareTo(last) < 0 ? first : last;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static @NonNull Object comparableMax(@NonNull Object first, @NonNull Object last) {
+ return ((Comparable<Object>) first).compareTo(last) >= 0 ? first : last;
+ }
+
+ private static @NonNull Object numberAdd(@NonNull Object first, @NonNull Object last) {
+ if (first instanceof Integer) {
+ return ((Integer) first) + ((Integer) last);
+ } else if (first instanceof Long) {
+ return ((Long) first) + ((Long) last);
+ } else if (first instanceof Float) {
+ return ((Float) first) + ((Float) last);
+ } else if (first instanceof Double) {
+ return ((Double) first) + ((Double) last);
+ } else {
+ throw new IllegalArgumentException("Unable to add " + first.getClass());
+ }
+ }
+
+ private static @NonNull Number numberIncrementFirst(@NonNull Object first,
+ @NonNull Object last) {
+ if (first instanceof Integer) {
+ return ((Integer) first) + 1;
+ } else if (first instanceof Long) {
+ return ((Long) first) + 1L;
+ } else {
+ throw new IllegalArgumentException("Unable to add " + first.getClass());
+ }
+ }
+
+ private static @NonNull Object booleanAnd(@NonNull Object first, @NonNull Object last) {
+ return ((Boolean) first) && ((Boolean) last);
+ }
+
+ private static @NonNull Object booleanOr(@NonNull Object first, @NonNull Object last) {
+ return ((Boolean) first) || ((Boolean) last);
+ }
+
+ private static @NonNull Object arrayAppend(@NonNull Object first, @NonNull Object last) {
+ if (!first.getClass().isArray()) {
+ throw new IllegalArgumentException("Unable to append " + first.getClass());
+ }
+ final Class<?> clazz = first.getClass().getComponentType();
+ final int firstLength = Array.getLength(first);
+ final int lastLength = Array.getLength(last);
+ final Object res = Array.newInstance(clazz, firstLength + lastLength);
+ System.arraycopy(first, 0, res, 0, firstLength);
+ System.arraycopy(last, 0, res, firstLength, lastLength);
+ return res;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static @NonNull Object arrayListAppend(@NonNull Object first, @NonNull Object last) {
+ if (!(first instanceof ArrayList)) {
+ throw new IllegalArgumentException("Unable to append " + first.getClass());
+ }
+ final ArrayList<Object> firstList = (ArrayList<Object>) first;
+ final ArrayList<Object> lastList = (ArrayList<Object>) last;
+ final ArrayList<Object> res = new ArrayList<>(firstList.size() + lastList.size());
+ res.addAll(firstList);
+ res.addAll(lastList);
+ return res;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<BundleMerger> CREATOR =
+ new Parcelable.Creator<BundleMerger>() {
+ @Override
+ public BundleMerger createFromParcel(Parcel in) {
+ return new BundleMerger(in);
+ }
+
+ @Override
+ public BundleMerger[] newArray(int size) {
+ return new BundleMerger[size];
+ }
+ };
+}
diff --git a/core/java/android/os/PermissionEnforcer.java b/core/java/android/os/PermissionEnforcer.java
new file mode 100644
index 0000000..221e89a
--- /dev/null
+++ b/core/java/android/os/PermissionEnforcer.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.SystemService;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.PermissionChecker;
+import android.permission.PermissionCheckerManager;
+
+/**
+ * PermissionEnforcer check permissions for AIDL-generated services which use
+ * the @EnforcePermission annotation.
+ *
+ * <p>AIDL services may be annotated with @EnforcePermission which will trigger
+ * the generation of permission check code. This generated code relies on
+ * PermissionEnforcer to validate the permissions. The methods available are
+ * purposely similar to the AIDL annotation syntax.
+ *
+ * @see android.permission.PermissionManager
+ *
+ * @hide
+ */
+@SystemService(Context.PERMISSION_ENFORCER_SERVICE)
+public class PermissionEnforcer {
+
+ private final Context mContext;
+
+ /** Protected constructor. Allows subclasses to instantiate an object
+ * without using a Context.
+ */
+ protected PermissionEnforcer() {
+ mContext = null;
+ }
+
+ /** Constructor, prefer using the fromContext static method when possible */
+ public PermissionEnforcer(@NonNull Context context) {
+ mContext = context;
+ }
+
+ @PermissionCheckerManager.PermissionResult
+ protected int checkPermission(@NonNull String permission, @NonNull AttributionSource source) {
+ return PermissionChecker.checkPermissionForDataDelivery(
+ mContext, permission, PermissionChecker.PID_UNKNOWN, source, "" /* message */);
+ }
+
+ public void enforcePermission(@NonNull String permission, @NonNull
+ AttributionSource source) throws SecurityException {
+ int result = checkPermission(permission, source);
+ if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Access denied, requires: " + permission);
+ }
+ }
+
+ public void enforcePermissionAllOf(@NonNull String[] permissions,
+ @NonNull AttributionSource source) throws SecurityException {
+ for (String permission : permissions) {
+ int result = checkPermission(permission, source);
+ if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Access denied, requires: allOf={"
+ + String.join(", ", permissions) + "}");
+ }
+ }
+ }
+
+ public void enforcePermissionAnyOf(@NonNull String[] permissions,
+ @NonNull AttributionSource source) throws SecurityException {
+ for (String permission : permissions) {
+ int result = checkPermission(permission, source);
+ if (result == PermissionCheckerManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+ throw new SecurityException("Access denied, requires: anyOf={"
+ + String.join(", ", permissions) + "}");
+ }
+
+ /**
+ * Returns a new PermissionEnforcer based on a Context.
+ *
+ * @hide
+ */
+ public static PermissionEnforcer fromContext(@NonNull Context context) {
+ return context.getSystemService(PermissionEnforcer.class);
+ }
+}
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index acfd15c..02704f5 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -22,12 +22,12 @@
import android.annotation.Nullable;
import android.util.ArrayMap;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 522807b..5dffa0a 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -22,10 +22,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 2dcf674..f2143f6 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -98,6 +98,10 @@
return mServiceManager.updatableViaApex(name);
}
+ public String[] getUpdatableNames(String apexName) throws RemoteException {
+ return mServiceManager.getUpdatableNames(apexName);
+ }
+
public ConnectionInfo getConnectionInfo(String name) throws RemoteException {
return mServiceManager.getConnectionInfo(name);
}
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index d2d6bec..4a6772d 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -20,8 +20,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.text.TextUtils;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java
index e1ec5cd..6b4a5cf 100644
--- a/core/java/android/os/UserBatteryConsumer.java
+++ b/core/java/android/os/UserBatteryConsumer.java
@@ -17,8 +17,9 @@
package android.os;
import android.annotation.NonNull;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cd2bbeb..4502eec 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7135,7 +7135,7 @@
* Format like "ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0"
* where imeId is ComponentName and subtype is int32.
*/
- @Readable
+ @Readable(maxTargetSdk = Build.VERSION_CODES.TIRAMISU)
public static final String ENABLED_INPUT_METHODS = "enabled_input_methods";
/**
@@ -7144,7 +7144,7 @@
* by ':'.
* @hide
*/
- @Readable
+ @Readable(maxTargetSdk = Build.VERSION_CODES.TIRAMISU)
public static final String DISABLED_SYSTEM_INPUT_METHODS = "disabled_system_input_methods";
/**
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index e285b1c..eb9901a 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -48,12 +48,12 @@
import android.util.ArraySet;
import android.util.PluralsMessageFormatter;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index a59d429..c6cd708 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -81,7 +81,6 @@
import android.view.InputEventReceiver;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.MotionEvent;
import android.view.PixelCopy;
import android.view.Surface;
@@ -251,7 +250,6 @@
final Rect mDispatchedStableInsets = new Rect();
DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT;
final InsetsState mInsetsState = new InsetsState();
- final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0];
final MergedConfiguration mMergedConfiguration = new MergedConfiguration();
final Bundle mSyncSeqIdBundle = new Bundle();
@@ -1133,8 +1131,9 @@
InputChannel inputChannel = new InputChannel();
if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE,
- mDisplay.getDisplayId(), mRequestedVisibilities, inputChannel,
- mInsetsState, mTempControls, new Rect(), new float[1]) < 0) {
+ mDisplay.getDisplayId(), WindowInsets.Type.defaultVisible(),
+ inputChannel, mInsetsState, mTempControls, new Rect(),
+ new float[1]) < 0) {
Log.w(TAG, "Failed to add window while updating wallpaper surface.");
return;
}
diff --git a/core/java/android/util/CharsetUtils.java b/core/java/android/util/CharsetUtils.java
index 3b08c3b..7c83087 100644
--- a/core/java/android/util/CharsetUtils.java
+++ b/core/java/android/util/CharsetUtils.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
+import com.android.modules.utils.ModifiedUtf8;
+
import dalvik.annotation.optimization.FastNative;
/**
@@ -30,8 +32,7 @@
* Callers are cautioned that there is a long-standing ART bug that emits
* non-standard 4-byte sequences, as described by {@code kUtfUse4ByteSequence}
* in {@code art/runtime/jni/jni_internal.cc}. If precise modified UTF-8
- * encoding is required, use {@link com.android.internal.util.ModifiedUtf8}
- * instead.
+ * encoding is required, use {@link ModifiedUtf8} instead.
*
* @hide
*/
@@ -43,8 +44,8 @@
* Callers are cautioned that there is a long-standing ART bug that emits
* non-standard 4-byte sequences, as described by
* {@code kUtfUse4ByteSequence} in {@code art/runtime/jni/jni_internal.cc}.
- * If precise modified UTF-8 encoding is required, use
- * {@link com.android.internal.util.ModifiedUtf8} instead.
+ * If precise modified UTF-8 encoding is required, use {@link ModifiedUtf8}
+ * instead.
*
* @param src string value to be encoded
* @param dest destination byte array to encode into
@@ -66,8 +67,8 @@
* Callers are cautioned that there is a long-standing ART bug that emits
* non-standard 4-byte sequences, as described by
* {@code kUtfUse4ByteSequence} in {@code art/runtime/jni/jni_internal.cc}.
- * If precise modified UTF-8 encoding is required, use
- * {@link com.android.internal.util.ModifiedUtf8} instead.
+ * If precise modified UTF-8 encoding is required, use {@link ModifiedUtf8}
+ * instead.
*
* @param src string value to be encoded
* @param srcLen exact length of string to be encoded
@@ -88,8 +89,8 @@
* Callers are cautioned that there is a long-standing ART bug that emits
* non-standard 4-byte sequences, as described by
* {@code kUtfUse4ByteSequence} in {@code art/runtime/jni/jni_internal.cc}.
- * If precise modified UTF-8 encoding is required, use
- * {@link com.android.internal.util.ModifiedUtf8} instead.
+ * If precise modified UTF-8 encoding is required, use {@link ModifiedUtf8}
+ * instead.
*
* @param src source byte array to decode from
* @param srcOff offset into source where decoding should begin
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 0a3e6b1..517d982 100755
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -174,6 +174,14 @@
* This is not a density that applications should target, instead relying
* on the system to scale their {@link #DENSITY_XXXHIGH} assets for them.
*/
+ public static final int DENSITY_520 = 520;
+
+ /**
+ * Intermediate density for screens that sit somewhere between
+ * {@link #DENSITY_XXHIGH} (480 dpi) and {@link #DENSITY_XXXHIGH} (640 dpi).
+ * This is not a density that applications should target, instead relying
+ * on the system to scale their {@link #DENSITY_XXXHIGH} assets for them.
+ */
public static final int DENSITY_560 = 560;
/**
diff --git a/core/java/android/util/TypedXmlPullParser.java b/core/java/android/util/TypedXmlPullParser.java
deleted file mode 100644
index aa68bf4..0000000
--- a/core/java/android/util/TypedXmlPullParser.java
+++ /dev/null
@@ -1,330 +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 android.util;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-/**
- * Specialization of {@link XmlPullParser} which adds explicit methods to
- * support consistent and efficient conversion of primitive data types.
- *
- * @hide
- */
-public interface TypedXmlPullParser extends XmlPullParser {
- /**
- * @return index of requested attribute, otherwise {@code -1} if undefined
- */
- default int getAttributeIndex(@Nullable String namespace, @NonNull String name) {
- final boolean namespaceNull = (namespace == null);
- final int count = getAttributeCount();
- for (int i = 0; i < count; i++) {
- if ((namespaceNull || namespace.equals(getAttributeNamespace(i)))
- && name.equals(getAttributeName(i))) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * @return index of requested attribute
- * @throws XmlPullParserException if the value is undefined
- */
- default int getAttributeIndexOrThrow(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) {
- throw new XmlPullParserException("Missing attribute " + name);
- } else {
- return index;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- @NonNull byte[] getAttributeBytesHex(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- @NonNull byte[] getAttributeBytesBase64(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- int getAttributeInt(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- int getAttributeIntHex(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- long getAttributeLong(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- long getAttributeLongHex(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- float getAttributeFloat(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- double getAttributeDouble(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- boolean getAttributeBoolean(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default @NonNull byte[] getAttributeBytesHex(@Nullable String namespace,
- @NonNull String name) throws XmlPullParserException {
- return getAttributeBytesHex(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default @NonNull byte[] getAttributeBytesBase64(@Nullable String namespace,
- @NonNull String name) throws XmlPullParserException {
- return getAttributeBytesBase64(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default int getAttributeInt(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeInt(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default int getAttributeIntHex(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeIntHex(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default long getAttributeLong(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeLong(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default long getAttributeLongHex(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeLongHex(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default float getAttributeFloat(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeFloat(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default double getAttributeDouble(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeDouble(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default boolean getAttributeBoolean(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeBoolean(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default @Nullable byte[] getAttributeBytesHex(@Nullable String namespace,
- @NonNull String name, @Nullable byte[] defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeBytesHex(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default @Nullable byte[] getAttributeBytesBase64(@Nullable String namespace,
- @NonNull String name, @Nullable byte[] defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeBytesBase64(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default int getAttributeInt(@Nullable String namespace, @NonNull String name,
- int defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeInt(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default int getAttributeIntHex(@Nullable String namespace, @NonNull String name,
- int defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeIntHex(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default long getAttributeLong(@Nullable String namespace, @NonNull String name,
- long defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeLong(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default long getAttributeLongHex(@Nullable String namespace, @NonNull String name,
- long defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeLongHex(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default float getAttributeFloat(@Nullable String namespace, @NonNull String name,
- float defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeFloat(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default double getAttributeDouble(@Nullable String namespace, @NonNull String name,
- double defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeDouble(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default boolean getAttributeBoolean(@Nullable String namespace, @NonNull String name,
- boolean defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeBoolean(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-}
diff --git a/core/java/android/util/TypedXmlSerializer.java b/core/java/android/util/TypedXmlSerializer.java
deleted file mode 100644
index 3f9eaa8..0000000
--- a/core/java/android/util/TypedXmlSerializer.java
+++ /dev/null
@@ -1,103 +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 android.util;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-
-/**
- * Specialization of {@link XmlSerializer} which adds explicit methods to
- * support consistent and efficient conversion of primitive data types.
- *
- * @hide
- */
-public interface TypedXmlSerializer extends XmlSerializer {
- /**
- * Functionally equivalent to {@link #attribute(String, String, String)} but
- * with the additional signal that the given value is a candidate for being
- * canonicalized, similar to {@link String#intern()}.
- */
- @NonNull XmlSerializer attributeInterned(@Nullable String namespace, @NonNull String name,
- @NonNull String value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeBytesHex(@Nullable String namespace, @NonNull String name,
- @NonNull byte[] value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeBytesBase64(@Nullable String namespace, @NonNull String name,
- @NonNull byte[] value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeInt(@Nullable String namespace, @NonNull String name,
- int value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeIntHex(@Nullable String namespace, @NonNull String name,
- int value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeLong(@Nullable String namespace, @NonNull String name,
- long value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeLongHex(@Nullable String namespace, @NonNull String name,
- long value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeFloat(@Nullable String namespace, @NonNull String name,
- float value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeDouble(@Nullable String namespace, @NonNull String name,
- double value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeBoolean(@Nullable String namespace, @NonNull String name,
- boolean value) throws IOException;
-}
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
index 38decf9..33058d8 100644
--- a/core/java/android/util/Xml.java
+++ b/core/java/android/util/Xml.java
@@ -22,10 +22,13 @@
import android.system.ErrnoException;
import android.system.Os;
-import com.android.internal.util.BinaryXmlPullParser;
-import com.android.internal.util.BinaryXmlSerializer;
+import com.android.internal.util.ArtBinaryXmlPullParser;
+import com.android.internal.util.ArtBinaryXmlSerializer;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.BinaryXmlSerializer;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.util.XmlObjectFactory;
@@ -146,7 +149,7 @@
* @hide
*/
public static @NonNull TypedXmlPullParser newBinaryPullParser() {
- return new BinaryXmlPullParser();
+ return new ArtBinaryXmlPullParser();
}
/**
@@ -225,7 +228,7 @@
* @hide
*/
public static @NonNull TypedXmlSerializer newBinarySerializer() {
- return new BinaryXmlSerializer();
+ return new ArtBinaryXmlSerializer();
}
/**
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index 1940042..0769f12 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -19,7 +19,6 @@
import android.content.ComponentName;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
/**
* Singular controller of insets to use when there isn't another obvious controller available.
@@ -32,10 +31,9 @@
* Called when top focused window changes to determine whether or not to take over insets
* control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
* @param component: Passes the top application component in the focused window.
- * @param requestedVisibilities The insets visibilities requested by the focussed window.
+ * @param requestedVisibleTypes The insets types requested visible by the focused window.
*/
- void topFocusedWindowChanged(in ComponentName component,
- in InsetsVisibilities insetsVisibilities);
+ void topFocusedWindowChanged(in ComponentName component, int requestedVisibleTypes);
/**
* @see IWindow#insetsChanged
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index dddbe39..e2bc566 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -721,7 +721,7 @@
* Called when a remote process updates the requested visibilities of insets on a display window
* container.
*/
- void updateDisplayWindowRequestedVisibilities(int displayId, in InsetsVisibilities vis);
+ void updateDisplayWindowRequestedVisibleTypes(int displayId, int requestedVisibleTypes);
/**
* Called to get the expected window insets.
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 0052e82..03ccb47 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -32,7 +32,6 @@
import android.view.WindowManager;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -48,15 +47,15 @@
*/
interface IWindowSession {
int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
- in int viewVisibility, in int layerStackId, in InsetsVisibilities requestedVisibilities,
+ in int viewVisibility, in int layerStackId, int requestedVisibleTypes,
out InputChannel outInputChannel, out InsetsState insetsState,
out InsetsSourceControl[] activeControls, out Rect attachedFrame,
out float[] sizeCompatScale);
int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
- in int viewVisibility, in int layerStackId, in int userId,
- in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel,
- out InsetsState insetsState, out InsetsSourceControl[] activeControls,
- out Rect attachedFrame, out float[] sizeCompatScale);
+ in int viewVisibility, in int layerStackId, in int userId, int requestedVisibleTypes,
+ out InputChannel outInputChannel, out InsetsState insetsState,
+ out InsetsSourceControl[] activeControls, out Rect attachedFrame,
+ out float[] sizeCompatScale);
int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, out InsetsState insetsState,
out Rect attachedFrame, out float[] sizeCompatScale);
@@ -279,9 +278,9 @@
oneway void updateTapExcludeRegion(IWindow window, in Region region);
/**
- * Updates the requested visibilities of insets.
+ * Updates the requested visible types of insets.
*/
- oneway void updateRequestedVisibilities(IWindow window, in InsetsVisibilities visibilities);
+ oneway void updateRequestedVisibleTypes(IWindow window, int requestedVisibleTypes);
/**
* Called when the system gesture exclusion has changed.
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4a72a62..8b38e9e 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -46,6 +46,7 @@
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSourceConsumer.ShowResult;
import android.view.InsetsState.InternalInsetsType;
@@ -102,18 +103,18 @@
void applySurfaceParams(final SyncRtSurfaceTransactionApplier.SurfaceParams... params);
/**
- * @see ViewRootImpl#updateCompatSysUiVisibility(int, boolean, boolean)
+ * @see ViewRootImpl#updateCompatSysUiVisibility(int, int, int)
*/
- void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
- boolean hasControl);
+ default void updateCompatSysUiVisibility(@InsetsType int visibleTypes,
+ @InsetsType int requestedVisibleTypes, @InsetsType int controllableTypes) { }
/**
* Called when the requested visibilities of insets have been modified by the client.
* The visibilities should be reported back to WM.
*
- * @param visibilities A collection of the requested visibilities.
+ * @param types Bitwise flags of types requested visible.
*/
- void updateRequestedVisibilities(InsetsVisibilities visibilities);
+ void updateRequestedVisibleTypes(@InsetsType int types);
/**
* @return Whether the host has any callbacks it wants to synchronize the animations with.
@@ -564,9 +565,6 @@
/** The state dispatched from server */
private final InsetsState mLastDispatchedState = new InsetsState();
- /** The requested visibilities sent to server */
- private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
-
private final Rect mFrame = new Rect();
private final BiFunction<InsetsController, Integer, InsetsSourceConsumer> mConsumerCreator;
private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>();
@@ -575,7 +573,6 @@
private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
private final ArrayList<RunningAnimation> mRunningAnimations = new ArrayList<>();
- private final ArraySet<InsetsSourceConsumer> mRequestedVisibilityChanged = new ArraySet<>();
private WindowInsets mLastInsets;
private boolean mAnimCallbackScheduled;
@@ -593,6 +590,7 @@
private boolean mStartingAnimation;
private int mCaptionInsetsHeight = 0;
private boolean mAnimationsDisabled;
+ private boolean mCompatSysUiVisibilityStaled;
private final Runnable mPendingControlTimeout = this::abortPendingImeControlRequest;
private final ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
@@ -604,6 +602,18 @@
/** Set of inset types which cannot be controlled by the user animation */
private @InsetsType int mDisabledUserAnimationInsetsTypes;
+ /** Set of inset types which are visible */
+ private @InsetsType int mVisibleTypes = WindowInsets.Type.defaultVisible();
+
+ /** Set of inset types which are requested visible */
+ private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
+
+ /** Set of inset types which are requested visible which are reported to the host */
+ private @InsetsType int mReportedRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
+
+ /** Set of inset types that we have controls of */
+ private @InsetsType int mControllableTypes;
+
private final Runnable mInvokeControllableInsetsChangedListeners =
this::invokeControllableInsetsChangedListeners;
@@ -687,8 +697,8 @@
}
@Override
- public boolean isRequestedVisible(int type) {
- return getSourceConsumer(type).isRequestedVisible();
+ public @InsetsType int getRequestedVisibleTypes() {
+ return mRequestedVisibleTypes;
}
public InsetsState getLastDispatchedState() {
@@ -715,6 +725,7 @@
final InsetsState lastState = new InsetsState(mState, true /* copySources */);
updateState(state);
applyLocalVisibilityOverride();
+ updateCompatSysUiVisibility();
if (!mState.equals(lastState, false /* excludingCaptionInsets */,
true /* excludeInvisibleIme */)) {
@@ -727,14 +738,15 @@
private void updateState(InsetsState newState) {
mState.set(newState, 0 /* types */);
+ @InsetsType int visibleTypes = 0;
@InsetsType int disabledUserAnimationTypes = 0;
@InsetsType int[] cancelledUserAnimationTypes = {0};
for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
InsetsSource source = newState.peekSource(type);
if (source == null) continue;
@AnimationType int animationType = getAnimationType(type);
+ @InsetsType int insetsType = toPublicType(type);
if (!source.isUserControllable()) {
- @InsetsType int insetsType = toPublicType(type);
// The user animation is not allowed when visible frame is empty.
disabledUserAnimationTypes |= insetsType;
if (animationType == ANIMATION_TYPE_USER) {
@@ -744,6 +756,15 @@
}
}
getSourceConsumer(type).updateSource(source, animationType);
+ if (source.isVisible()) {
+ visibleTypes |= insetsType;
+ }
+ }
+ if (mVisibleTypes != visibleTypes) {
+ if (WindowInsets.Type.hasCompatSystemBars(mVisibleTypes ^ visibleTypes)) {
+ mCompatSysUiVisibilityStaled = true;
+ }
+ mVisibleTypes = visibleTypes;
}
for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
// Only update the server side insets here.
@@ -829,7 +850,8 @@
}
/**
- * @see InsetsState#calculateInsets
+ * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int,
+ * int, SparseIntArray)
*/
@VisibleForTesting
public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars,
@@ -868,7 +890,7 @@
}
}
- boolean requestedVisibilityStale = false;
+ @InsetsType int controllableTypes = 0;
final int[] showTypes = new int[1];
final int[] hideTypes = new int[1];
@@ -888,22 +910,7 @@
final @InternalInsetsType int type = control.getType();
final InsetsSourceConsumer consumer = getSourceConsumer(type);
consumer.setControl(control, showTypes, hideTypes);
-
- if (!requestedVisibilityStale) {
- final boolean requestedVisible = consumer.isRequestedVisible();
-
- // We might have changed our requested visibilities while we don't have the control,
- // so we need to update our requested state once we have control. Otherwise, our
- // requested state at the server side might be incorrect.
- final boolean requestedVisibilityChanged =
- requestedVisible != mRequestedVisibilities.getVisibility(type);
-
- // The IME client visibility will be reset by insets source provider while updating
- // control, so if IME is requested visible, we need to send the request to server.
- final boolean imeRequestedVisible = type == ITYPE_IME && requestedVisible;
-
- requestedVisibilityStale = requestedVisibilityChanged || imeRequestedVisible;
- }
+ controllableTypes |= InsetsState.toPublicType(type);
}
if (mTmpControlArray.size() > 0) {
@@ -927,8 +934,15 @@
applyAnimation(hideTypes[0], false /* show */, false /* fromIme */);
}
+ if (mControllableTypes != controllableTypes) {
+ if (WindowInsets.Type.hasCompatSystemBars(mControllableTypes ^ controllableTypes)) {
+ mCompatSysUiVisibilityStaled = true;
+ }
+ mControllableTypes = controllableTypes;
+ }
+
// InsetsSourceConsumer#setControl might change the requested visibility.
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
}
@Override
@@ -1082,7 +1096,7 @@
if (types == 0) {
// nothing to animate.
listener.onCancelled(null);
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
if (DEBUG) Log.d(TAG, "no types to animate in controlAnimationUnchecked");
return;
}
@@ -1118,7 +1132,7 @@
}
});
}
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
return;
}
@@ -1126,7 +1140,7 @@
if (typesReady == 0) {
if (DEBUG) Log.d(TAG, "No types ready. onCancelled()");
listener.onCancelled(null);
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
return;
}
@@ -1158,7 +1172,7 @@
} else {
hideDirectly(types, false /* animationFinished */, animationType, fromIme);
}
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
}
// TODO(b/242962223): Make this setter restrictive.
@@ -1386,11 +1400,14 @@
}
/**
- * @see ViewRootImpl#updateCompatSysUiVisibility(int, boolean, boolean)
+ * @see ViewRootImpl#updateCompatSysUiVisibility(int, int, int)
*/
- public void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
- boolean hasControl) {
- mHost.updateCompatSysUiVisibility(type, visible, hasControl);
+ public void updateCompatSysUiVisibility() {
+ if (mCompatSysUiVisibilityStaled) {
+ mCompatSysUiVisibilityStaled = false;
+ mHost.updateCompatSysUiVisibility(
+ mVisibleTypes, mRequestedVisibleTypes, mControllableTypes);
+ }
}
/**
@@ -1420,35 +1437,27 @@
@VisibleForTesting
public void onRequestedVisibilityChanged(InsetsSourceConsumer consumer) {
- mRequestedVisibilityChanged.add(consumer);
+ final @InsetsType int type = InsetsState.toPublicType(consumer.getType());
+ final int requestedVisibleTypes = consumer.isRequestedVisible()
+ ? mRequestedVisibleTypes | type
+ : mRequestedVisibleTypes & ~type;
+ if (mRequestedVisibleTypes != requestedVisibleTypes) {
+ mRequestedVisibleTypes = requestedVisibleTypes;
+ if (WindowInsets.Type.hasCompatSystemBars(type)) {
+ mCompatSysUiVisibilityStaled = true;
+ }
+ }
}
/**
- * Sends the requested visibilities to window manager if any of them is changed.
+ * Sends the requested visible types to window manager if any of them is changed.
*/
- private void updateRequestedVisibilities() {
- boolean changed = false;
- for (int i = mRequestedVisibilityChanged.size() - 1; i >= 0; i--) {
- final InsetsSourceConsumer consumer = mRequestedVisibilityChanged.valueAt(i);
- final @InternalInsetsType int type = consumer.getType();
- if (type == ITYPE_CAPTION_BAR) {
- continue;
- }
- final boolean requestedVisible = consumer.isRequestedVisible();
- if (mRequestedVisibilities.getVisibility(type) != requestedVisible) {
- mRequestedVisibilities.setVisibility(type, requestedVisible);
- changed = true;
- }
+ private void reportRequestedVisibleTypes() {
+ updateCompatSysUiVisibility();
+ if (mReportedRequestedVisibleTypes != mRequestedVisibleTypes) {
+ mReportedRequestedVisibleTypes = mRequestedVisibleTypes;
+ mHost.updateRequestedVisibleTypes(mReportedRequestedVisibleTypes);
}
- mRequestedVisibilityChanged.clear();
- if (!changed) {
- return;
- }
- mHost.updateRequestedVisibilities(mRequestedVisibilities);
- }
-
- InsetsVisibilities getRequestedVisibilities() {
- return mRequestedVisibilities;
}
@VisibleForTesting
@@ -1504,7 +1513,7 @@
for (int i = internalTypes.size() - 1; i >= 0; i--) {
getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished, animationType);
}
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
if (fromIme) {
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0);
@@ -1520,7 +1529,7 @@
for (int i = internalTypes.size() - 1; i >= 0; i--) {
getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */);
}
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
if (fromIme) {
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0);
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 7275780..da54da16 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -273,6 +273,9 @@
/**
* Class to describe the insets size to be provided to window with specific window type. If not
* used, same insets size will be sent as instructed in the insetsSize and source.
+ *
+ * If the insetsSize of given type is set to {@code null}, the insets source frame will be used
+ * directly for that window type.
*/
public static class InsetsSizeOverride implements Parcelable {
public final int windowType;
@@ -280,7 +283,7 @@
protected InsetsSizeOverride(Parcel in) {
windowType = in.readInt();
- insetsSize = in.readParcelable(null, android.graphics.Insets.class);
+ insetsSize = in.readParcelable(null, Insets.class);
}
public InsetsSizeOverride(int type, Insets size) {
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 5236fe7..7a498ad 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -34,7 +34,6 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.graphics.Rect;
-import android.util.ArraySet;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsState.InternalInsetsType;
@@ -149,9 +148,6 @@
source.setVisible(serverVisibility);
mController.notifyVisibilityChanged();
}
-
- // For updateCompatSysUiVisibility
- applyLocalVisibilityOverride();
} else {
final boolean requestedVisible = isRequestedVisibleAwaitingControl();
final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null;
@@ -259,8 +255,6 @@
mController.getHost().getInputMethodManager(), null /* icProto */);
}
- updateCompatSysUiVisibility(hasControl, source, isVisible);
-
// If we don't have control, we are not able to change the visibility.
if (!hasControl) {
if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in "
@@ -277,36 +271,6 @@
return true;
}
- private void updateCompatSysUiVisibility(boolean hasControl, InsetsSource source,
- boolean visible) {
- final @InsetsType int publicType = InsetsState.toPublicType(mType);
- if (publicType != WindowInsets.Type.statusBars()
- && publicType != WindowInsets.Type.navigationBars()) {
- // System UI visibility only controls status bars and navigation bars.
- return;
- }
- final boolean compatVisible;
- if (hasControl) {
- compatVisible = mRequestedVisible;
- } else if (source != null && !source.getFrame().isEmpty()) {
- compatVisible = visible;
- } else {
- final ArraySet<Integer> types = InsetsState.toInternalType(publicType);
- for (int i = types.size() - 1; i >= 0; i--) {
- final InsetsSource s = mState.peekSource(types.valueAt(i));
- if (s != null && !s.getFrame().isEmpty()) {
- // The compat system UI visibility would be updated by another consumer which
- // handles the same public insets type.
- return;
- }
- }
- // No one provides the public type. Use the requested visibility for making the callback
- // behavior compatible.
- compatVisible = mRequestedVisible;
- }
- mController.updateCompatSysUiVisibility(mType, compatVisible, hasControl);
- }
-
@VisibleForTesting
public boolean isRequestedVisible() {
return mRequestedVisible;
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 9f426a1..e91839b 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -352,7 +352,7 @@
}
public Insets calculateInsets(Rect frame, @InsetsType int types,
- InsetsVisibilities overrideVisibilities) {
+ @InsetsType int requestedVisibleTypes) {
Insets insets = Insets.NONE;
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
InsetsSource source = mSources[type];
@@ -360,10 +360,7 @@
continue;
}
int publicType = InsetsState.toPublicType(type);
- if ((publicType & types) == 0) {
- continue;
- }
- if (!overrideVisibilities.getVisibility(type)) {
+ if ((publicType & types & requestedVisibleTypes) == 0) {
continue;
}
insets = Insets.max(source.calculateInsets(frame, true), insets);
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index ceab310..a08a5a8 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1294,6 +1294,8 @@
// NOTE: If you add a new axis here you must also add it to:
// frameworks/native/include/android/input.h
// frameworks/native/libs/input/InputEventLabels.cpp
+ // platform/cts/tests/tests/view/src/android/view/cts/MotionEventTest.java
+ // (testAxisFromToString)
// Symbolic names of all axes.
private static final SparseArray<String> AXIS_SYMBOLIC_NAMES = new SparseArray<String>();
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index 3fe9110..e8f62fc 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -45,6 +45,7 @@
= new ArrayList<>();
private int mCaptionInsetsHeight = 0;
private WindowInsetsAnimationControlListener mLoggingListener;
+ private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
@Override
public void show(int types) {
@@ -52,6 +53,7 @@
mReplayedInsetsController.show(types);
} else {
mRequests.add(new ShowRequest(types));
+ mRequestedVisibleTypes |= types;
}
}
@@ -61,6 +63,7 @@
mReplayedInsetsController.hide(types);
} else {
mRequests.add(new HideRequest(types));
+ mRequestedVisibleTypes &= ~types;
}
}
@@ -122,11 +125,11 @@
}
@Override
- public boolean isRequestedVisible(int type) {
-
- // Method is only used once real insets controller is attached, so no need to traverse
- // requests here.
- return InsetsState.getDefaultVisibility(type);
+ public @InsetsType int getRequestedVisibleTypes() {
+ if (mReplayedInsetsController != null) {
+ return mReplayedInsetsController.getRequestedVisibleTypes();
+ }
+ return mRequestedVisibleTypes;
}
@Override
@@ -189,6 +192,7 @@
mAppearanceMask = 0;
mAnimationsDisabled = false;
mLoggingListener = null;
+ mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
// After replaying, we forward everything directly to the replayed instance.
mReplayedInsetsController = controller;
}
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index a52fc75..6d25523 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -183,6 +183,7 @@
private static native float nativeGetVelocity(long ptr, int axis, int id);
private static native boolean nativeGetEstimator(
long ptr, int axis, int id, Estimator outEstimator);
+ private static native boolean nativeIsAxisSupported(int axis);
static {
// Strategy string and IDs mapping lookup.
@@ -305,6 +306,22 @@
}
/**
+ * Checks whether a given motion axis is supported for velocity tracking.
+ *
+ * <p>The axis values that would make sense to use for this method are the ones defined in the
+ * {@link MotionEvent} class.
+ *
+ * @param axis The axis to check for velocity support.
+ * @return {@code true} if {@code axis} is supported for velocity tracking, or {@code false}
+ * otherwise.
+ * @see #getAxisVelocity(int, int)
+ * @see #getAxisVelocity(int)
+ */
+ public boolean isAxisSupported(int axis) {
+ return nativeIsAxisSupported(axis);
+ }
+
+ /**
* Reset the velocity tracker back to its initial state.
*/
public void clear() {
@@ -345,7 +362,9 @@
* {@link #getYVelocity()}.
*
* @param units The units you would like the velocity in. A value of 1
- * provides pixels per millisecond, 1000 provides pixels per second, etc.
+ * provides units per millisecond, 1000 provides units per second, etc.
+ * Note that the units referred to here are the same units with which motion is reported. For
+ * axes X and Y, the units are pixels.
* @param maxVelocity The maximum velocity that can be computed by this method.
* This value must be declared in the same unit as the units parameter. This value
* must be positive.
@@ -397,6 +416,45 @@
}
/**
+ * Retrieve the last computed velocity for a given motion axis. You must first call
+ * {@link #computeCurrentVelocity(int)} or {@link #computeCurrentVelocity(int, float)} before
+ * calling this function.
+ *
+ * <p>In addition to {@link MotionEvent#AXIS_X} and {@link MotionEvent#AXIS_Y} which have been
+ * supported since the introduction of this class, the following axes are supported for this
+ * method:
+ * <ul>
+ * <li> {@link MotionEvent#AXIS_SCROLL}: supported starting
+ * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+ * </ul>
+ *
+ * @param axis Which axis' velocity to return.
+ * @param id Which pointer's velocity to return.
+ * @return The previously computed velocity for {@code axis} for pointer ID of {@code id} if
+ * {@code axis} is supported for velocity tracking, or 0 if velocity tracking is not supported
+ * for the axis.
+ * @see #isAxisSupported(int)
+ */
+ public float getAxisVelocity(int axis, int id) {
+ return nativeGetVelocity(mPtr, axis, id);
+ }
+
+ /**
+ * Equivalent to calling {@link #getAxisVelocity(int, int)} for {@code axis} and the active
+ * pointer.
+ *
+ * @param axis Which axis' velocity to return.
+ * @return The previously computed velocity for {@code axis} for the active pointer if
+ * {@code axis} is supported for velocity tracking, or 0 if velocity tracking is not supported
+ * for the axis.
+ * @see #isAxisSupported(int)
+ * @see #getAxisVelocity(int, int)
+ */
+ public float getAxisVelocity(int axis) {
+ return nativeGetVelocity(mPtr, axis, ACTIVE_POINTER_ID);
+ }
+
+ /**
* Get an estimator for the movements of a pointer using past movements of the
* pointer to predict future movements.
*
@@ -426,8 +484,8 @@
* Past estimated positions are at negative times and future estimated positions
* are at positive times.
*
- * First coefficient is position (in pixels), second is velocity (in pixels per second),
- * third is acceleration (in pixels per second squared).
+ * First coefficient is position (in units), second is velocity (in units per second),
+ * third is acceleration (in units per second squared).
*
* @hide For internal use only. Not a final API.
*/
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index efda257..ff4588a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -162,7 +162,6 @@
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.InputDevice.InputSourceClass;
-import android.view.InsetsState.InternalInsetsType;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl.Transaction;
import android.view.View.AttachInfo;
@@ -1245,7 +1244,7 @@
final float[] sizeCompatScale = { 1f };
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
- mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
+ mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
mTempControls, attachedFrame, sizeCompatScale);
if (!attachedFrame.isValid()) {
attachedFrame = null;
@@ -1284,7 +1283,7 @@
mWindowLayout.computeFrames(mWindowAttributes, state,
displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
- mInsetsController.getRequestedVisibilities(), 1f /* compactScale */,
+ mInsetsController.getRequestedVisibleTypes(), 1f /* compactScale */,
mTmpFrames);
setFrame(mTmpFrames.frame);
registerBackCallbackOnWindow();
@@ -2376,7 +2375,7 @@
mCompatibleVisibilityInfo.globalVisibility =
(mCompatibleVisibilityInfo.globalVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE)
| (mAttachInfo.mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE);
- dispatchDispatchSystemUiVisibilityChanged(mCompatibleVisibilityInfo);
+ dispatchDispatchSystemUiVisibilityChanged();
if (mAttachInfo.mKeepScreenOn != oldScreenOn
|| mAttachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility
|| mAttachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) {
@@ -2404,24 +2403,29 @@
/**
* Update the compatible system UI visibility for dispatching it to the legacy app.
- *
- * @param type Indicates which type of the insets source we are handling.
- * @param visible True if the insets source is visible.
- * @param hasControl True if we can control the insets source.
*/
- void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
- boolean hasControl) {
- @InsetsType final int publicType = InsetsState.toPublicType(type);
- if (publicType != Type.statusBars() && publicType != Type.navigationBars()) {
- return;
- }
+ void updateCompatSysUiVisibility(@InsetsType int visibleTypes,
+ @InsetsType int requestedVisibleTypes, @InsetsType int controllableTypes) {
+ // If a type is controllable, the visibility is overridden by the requested visibility.
+ visibleTypes =
+ (requestedVisibleTypes & controllableTypes) | (visibleTypes & ~controllableTypes);
+
+ updateCompatSystemUiVisibilityInfo(SYSTEM_UI_FLAG_FULLSCREEN, Type.statusBars(),
+ visibleTypes, controllableTypes);
+ updateCompatSystemUiVisibilityInfo(SYSTEM_UI_FLAG_HIDE_NAVIGATION, Type.navigationBars(),
+ visibleTypes, controllableTypes);
+ dispatchDispatchSystemUiVisibilityChanged();
+ }
+
+ private void updateCompatSystemUiVisibilityInfo(int systemUiFlag, @InsetsType int insetsType,
+ @InsetsType int visibleTypes, @InsetsType int controllableTypes) {
final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo;
- final int systemUiFlag = publicType == Type.statusBars()
- ? View.SYSTEM_UI_FLAG_FULLSCREEN
- : View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
- if (visible) {
+ final boolean willBeVisible = (visibleTypes & insetsType) != 0;
+ final boolean hasControl = (controllableTypes & insetsType) != 0;
+ final boolean wasInvisible = (mAttachInfo.mSystemUiVisibility & systemUiFlag) != 0;
+ if (willBeVisible) {
info.globalVisibility &= ~systemUiFlag;
- if (hasControl && (mAttachInfo.mSystemUiVisibility & systemUiFlag) != 0) {
+ if (hasControl && wasInvisible) {
// The local system UI visibility can only be cleared while we have the control.
info.localChanges |= systemUiFlag;
}
@@ -2429,7 +2433,6 @@
info.globalVisibility |= systemUiFlag;
info.localChanges &= ~systemUiFlag;
}
- dispatchDispatchSystemUiVisibilityChanged(info);
}
/**
@@ -2445,25 +2448,28 @@
&& (info.globalVisibility & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
info.globalVisibility &= ~SYSTEM_UI_FLAG_LOW_PROFILE;
info.localChanges |= SYSTEM_UI_FLAG_LOW_PROFILE;
- dispatchDispatchSystemUiVisibilityChanged(info);
+ dispatchDispatchSystemUiVisibilityChanged();
}
}
- private void dispatchDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
- if (mDispatchedSystemUiVisibility != args.globalVisibility) {
+ private void dispatchDispatchSystemUiVisibilityChanged() {
+ if (mDispatchedSystemUiVisibility != mCompatibleVisibilityInfo.globalVisibility) {
mHandler.removeMessages(MSG_DISPATCH_SYSTEM_UI_VISIBILITY);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, args));
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY));
}
}
- private void handleDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
- if (mView == null) return;
- if (args.localChanges != 0) {
- mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges);
- args.localChanges = 0;
+ private void handleDispatchSystemUiVisibilityChanged() {
+ if (mView == null) {
+ return;
+ }
+ final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo;
+ if (info.localChanges != 0) {
+ mView.updateLocalSystemUiVisibility(info.localValue, info.localChanges);
+ info.localChanges = 0;
}
- final int visibility = args.globalVisibility & View.SYSTEM_UI_CLEARABLE_FLAGS;
+ final int visibility = info.globalVisibility & View.SYSTEM_UI_CLEARABLE_FLAGS;
if (mDispatchedSystemUiVisibility != visibility) {
mDispatchedSystemUiVisibility = visibility;
mView.dispatchSystemUiVisibilityChanged(visibility);
@@ -4964,7 +4970,7 @@
}
void reportKeepClearAreasChanged() {
- if (!mHasPendingKeepClearAreaChange) {
+ if (!mHasPendingKeepClearAreaChange || mView == null) {
return;
}
mHasPendingKeepClearAreaChange = false;
@@ -5728,7 +5734,7 @@
handleDragEvent(event);
} break;
case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
- handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
+ handleDispatchSystemUiVisibilityChanged();
} break;
case MSG_UPDATE_CONFIGURATION: {
Configuration config = (Configuration) msg.obj;
@@ -8148,7 +8154,7 @@
state.getDisplayCutoutSafe(displayCutoutSafe);
mWindowLayout.computeFrames(mWindowAttributes.forRotation(winConfig.getRotation()),
state, displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
- measuredWidth, measuredHeight, mInsetsController.getRequestedVisibilities(),
+ measuredWidth, measuredHeight, mInsetsController.getRequestedVisibleTypes(),
1f /* compatScale */, mTmpFrames);
mWinFrameInScreen.set(mTmpFrames.frame);
if (mTranslator != null) {
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index d960ba1..c59d83e 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -145,15 +145,17 @@
}
@Override
- public void updateCompatSysUiVisibility(int type, boolean visible, boolean hasControl) {
- mViewRoot.updateCompatSysUiVisibility(type, visible, hasControl);
+ public void updateCompatSysUiVisibility(int visibleTypes, int requestedVisibleTypes,
+ int controllableTypes) {
+ mViewRoot.updateCompatSysUiVisibility(visibleTypes, requestedVisibleTypes,
+ controllableTypes);
}
@Override
- public void updateRequestedVisibilities(InsetsVisibilities vis) {
+ public void updateRequestedVisibleTypes(@WindowInsets.Type.InsetsType int types) {
try {
if (mViewRoot.mAdded) {
- mViewRoot.mWindowSession.updateRequestedVisibilities(mViewRoot.mWindow, vis);
+ mViewRoot.mWindowSession.updateRequestedVisibleTypes(mViewRoot.mWindow, types);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to call insetsModified", e);
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index c1dddbe..2a76c4e 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1429,6 +1429,8 @@
static final int LAST = GENERIC_OVERLAYS;
static final int SIZE = 10;
+ static final int DEFAULT_VISIBLE = ~IME;
+
static int indexOf(@InsetsType int type) {
switch (type) {
case STATUS_BARS:
@@ -1457,7 +1459,8 @@
}
}
- static String toString(@InsetsType int types) {
+ /** @hide */
+ public static String toString(@InsetsType int types) {
StringBuilder result = new StringBuilder();
if ((types & STATUS_BARS) != 0) {
result.append("statusBars |");
@@ -1598,6 +1601,15 @@
}
/**
+ * @return Default visible types.
+ *
+ * @hide
+ */
+ public static @InsetsType int defaultVisible() {
+ return DEFAULT_VISIBLE;
+ }
+
+ /**
* @return All inset types combined.
*
* @hide
@@ -1605,6 +1617,15 @@
public static @InsetsType int all() {
return 0xFFFFFFFF;
}
+
+ /**
+ * @return System bars which can be controlled by {@link View.SystemUiVisibility}.
+ *
+ * @hide
+ */
+ public static boolean hasCompatSystemBars(@InsetsType int types) {
+ return (types & (STATUS_BARS | NAVIGATION_BARS)) != 0;
+ }
}
/**
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 63f9e13..bc0bab7 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -23,7 +23,6 @@
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.CancellationSignal;
-import android.view.InsetsState.InternalInsetsType;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.animation.Interpolator;
@@ -279,11 +278,10 @@
InsetsState getState();
/**
- * @return Whether the specified insets source is currently requested to be visible by the
- * application.
+ * @return Insets types that have been requested to be visible.
* @hide
*/
- boolean isRequestedVisible(@InternalInsetsType int type);
+ @InsetsType int getRequestedVisibleTypes();
/**
* Adds a {@link OnControllableInsetsChangedListener} to the window insets controller.
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 5ed9d2f..7077804 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -40,6 +40,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
+import android.view.WindowInsets.Type.InsetsType;
import android.window.ClientWindowFrames;
/**
@@ -63,7 +64,7 @@
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
- int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities,
+ int requestedWidth, int requestedHeight, @InsetsType int requestedVisibleTypes,
float compatScale, ClientWindowFrames frames) {
final int type = attrs.type;
final int fl = attrs.flags;
@@ -130,7 +131,7 @@
&& (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|| cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
final Insets systemBarsInsets = state.calculateInsets(
- displayFrame, WindowInsets.Type.systemBars(), requestedVisibilities);
+ displayFrame, WindowInsets.Type.systemBars(), requestedVisibleTypes);
if (systemBarsInsets.left > 0) {
displayCutoutSafeExceptMaybeBars.left = MIN_X;
}
@@ -288,7 +289,7 @@
+ " displayCutoutSafe=" + displayCutoutSafe
+ " attrs=" + attrs
+ " state=" + state
- + " requestedVisibilities=" + requestedVisibilities);
+ + " requestedInvisibleTypes=" + WindowInsets.Type.toString(~requestedVisibleTypes));
}
public static void extendFrameByCutout(Rect displayCutoutSafe,
diff --git a/core/java/android/view/WindowlessWindowLayout.java b/core/java/android/view/WindowlessWindowLayout.java
index 5bec5b6..8ef4d78 100644
--- a/core/java/android/view/WindowlessWindowLayout.java
+++ b/core/java/android/view/WindowlessWindowLayout.java
@@ -18,6 +18,7 @@
import android.app.WindowConfiguration.WindowingMode;
import android.graphics.Rect;
+import android.view.WindowInsets.Type.InsetsType;
import android.window.ClientWindowFrames;
/**
@@ -29,7 +30,7 @@
@Override
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
- int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities,
+ int requestedWidth, int requestedHeight, @InsetsType int requestedVisibleTypes,
float compatScale, ClientWindowFrames frames) {
frames.frame.set(0, 0, attrs.width, attrs.height);
frames.displayFrame.set(frames.frame);
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index fbf7456..69340aa 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -30,6 +30,7 @@
import android.os.RemoteException;
import android.util.Log;
import android.util.MergedConfiguration;
+import android.view.WindowInsets.Type.InsetsType;
import android.window.ClientWindowFrames;
import android.window.OnBackInvokedCallbackInfo;
@@ -147,7 +148,7 @@
*/
@Override
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
+ int viewVisibility, int displayId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
@@ -198,11 +199,11 @@
*/
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
+ int viewVisibility, int displayId, int userId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
- return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibilities,
+ return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibleTypes,
outInputChannel, outInsetsState, outActiveControls, outAttachedFrame,
outSizeCompatScale);
}
@@ -491,7 +492,8 @@
}
@Override
- public void updateRequestedVisibilities(IWindow window, InsetsVisibilities visibilities) {
+ public void updateRequestedVisibleTypes(IWindow window,
+ @InsetsType int requestedVisibleTypes) {
}
@Override
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 121839b..bdfcb03 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -16,6 +16,7 @@
package android.view.inputmethod;
+import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -23,6 +24,7 @@
import android.content.res.Configuration;
import android.icu.text.DisplayContext;
import android.icu.text.LocaleDisplayNames;
+import android.icu.util.ULocale;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -74,6 +76,10 @@
/** {@hide} */
public static final int SUBTYPE_ID_NONE = 0;
+ private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+
+ private static final String UNDEFINED_LANGUAGE_TAG = "und";
+
private final boolean mIsAuxiliary;
private final boolean mOverridesImplicitlyEnabledSubtype;
private final boolean mIsAsciiCapable;
@@ -90,6 +96,14 @@
private volatile HashMap<String, String> mExtraValueHashMapCache;
/**
+ * A volatile cache to optimize {@link #getCanonicalizedLanguageTag()}.
+ *
+ * <p>{@code null} means that the initial evaluation is not yet done.</p>
+ */
+ @Nullable
+ private volatile String mCachedCanonicalizedLanguageTag;
+
+ /**
* InputMethodSubtypeBuilder is a builder class of InputMethodSubtype.
* This class is designed to be used with
* {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}.
@@ -392,6 +406,65 @@
}
/**
+ * Returns a canonicalized BCP 47 Language Tag initialized with {@link #getLocaleObject()}.
+ *
+ * <p>This has an internal cache mechanism. Subsequent calls are in general cheap and fast.</p>
+ *
+ * @return a canonicalized BCP 47 Language Tag initialized with {@link #getLocaleObject()}. An
+ * empty string if {@link #getLocaleObject()} returns {@code null} or an empty
+ * {@link Locale} object.
+ * @hide
+ */
+ @AnyThread
+ @NonNull
+ public String getCanonicalizedLanguageTag() {
+ final String cachedValue = mCachedCanonicalizedLanguageTag;
+ if (cachedValue != null) {
+ return cachedValue;
+ }
+
+ String result = null;
+ final Locale locale = getLocaleObject();
+ if (locale != null) {
+ final String langTag = locale.toLanguageTag();
+ if (!TextUtils.isEmpty(langTag)) {
+ result = ULocale.createCanonical(ULocale.forLanguageTag(langTag)).toLanguageTag();
+ }
+ }
+ result = TextUtils.emptyIfNull(result);
+ mCachedCanonicalizedLanguageTag = result;
+ return result;
+ }
+
+ /**
+ * Determines whether this {@link InputMethodSubtype} can be used as the key of mapping rules
+ * between {@link InputMethodSubtype} and hardware keyboard layout.
+ *
+ * <p>Note that in a future build may require different rules. Design the system so that the
+ * system can automatically take care of any rule changes upon OTAs.</p>
+ *
+ * @return {@code true} if this {@link InputMethodSubtype} can be used as the key of mapping
+ * rules between {@link InputMethodSubtype} and hardware keyboard layout.
+ * @hide
+ */
+ public boolean isSuitableForPhysicalKeyboardLayoutMapping() {
+ if (hashCode() == SUBTYPE_ID_NONE) {
+ return false;
+ }
+ if (!TextUtils.equals(getMode(), SUBTYPE_MODE_KEYBOARD)) {
+ return false;
+ }
+ if (isAuxiliary()) {
+ return false;
+ }
+ final String langTag = getCanonicalizedLanguageTag();
+ if (langTag.isEmpty() || TextUtils.equals(langTag, UNDEFINED_LANGUAGE_TAG)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* @return The mode of the subtype.
*/
public String getMode() {
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index d161037..1b64e61 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -25,7 +25,8 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
/**
@@ -181,11 +182,7 @@
*/
public TaskSnapshot taskSnapshot;
- /**
- * The requested insets visibility of the top main window.
- * @hide
- */
- public final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ public @InsetsType int requestedVisibleTypes = WindowInsets.Type.defaultVisible();
public StartingWindowInfo() {
@@ -218,7 +215,7 @@
dest.writeInt(splashScreenThemeResId);
dest.writeBoolean(isKeyguardOccluded);
dest.writeTypedObject(taskSnapshot, flags);
- requestedVisibilities.writeToParcel(dest, flags);
+ dest.writeInt(requestedVisibleTypes);
}
void readFromParcel(@NonNull Parcel source) {
@@ -232,7 +229,7 @@
splashScreenThemeResId = source.readInt();
isKeyguardOccluded = source.readBoolean();
taskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR);
- requestedVisibilities.readFromParcel(source);
+ requestedVisibleTypes = source.readInt();
}
@Override
diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
index 999be08..65372be 100644
--- a/core/java/com/android/internal/app/LocalePicker.java
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -314,8 +314,7 @@
try {
final IActivityManager am = ActivityManager.getService();
- final Configuration config = am.getConfiguration();
-
+ final Configuration config = new Configuration();
config.setLocales(locales);
config.userSetLocale = true;
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodManagerGlobal.java b/core/java/com/android/internal/inputmethod/IInputMethodManagerGlobal.java
index f0fe573..5392bdc 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodManagerGlobal.java
+++ b/core/java/com/android/internal/inputmethod/IInputMethodManagerGlobal.java
@@ -155,4 +155,21 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Invokes {@link IInputMethodManager#removeImeSurface()}
+ */
+ @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @AnyThread
+ public static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
+ final IInputMethodManager service = getService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.removeImeSurface();
+ } catch (RemoteException e) {
+ handleRemoteExceptionOrRethrow(e, exceptionHandler);
+ }
+ }
}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index a352063..3e988e6 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -20,8 +20,6 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.clearsCompatInsets;
import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
@@ -79,8 +77,6 @@
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.InputQueue;
-import android.view.InsetsState;
-import android.view.InsetsState.InternalInsetsType;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.LayoutInflater;
@@ -98,6 +94,7 @@
import android.view.Window;
import android.view.WindowCallbacks;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowManager;
@@ -145,13 +142,15 @@
new ColorViewAttributes(FLAG_TRANSLUCENT_STATUS,
Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
- com.android.internal.R.id.statusBarBackground, ITYPE_STATUS_BAR);
+ com.android.internal.R.id.statusBarBackground,
+ WindowInsets.Type.statusBars());
public static final ColorViewAttributes NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES =
new ColorViewAttributes(FLAG_TRANSLUCENT_NAVIGATION,
Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT,
Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
- com.android.internal.R.id.navigationBarBackground, ITYPE_NAVIGATION_BAR);
+ com.android.internal.R.id.navigationBarBackground,
+ WindowInsets.Type.navigationBars());
// This is used to workaround an issue where the PiP shadow can be transparent if the window
// background is transparent
@@ -1106,6 +1105,7 @@
int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
final WindowInsetsController controller = getWindowInsetsController();
+ final @InsetsType int requestedVisibleTypes = controller.getRequestedVisibleTypes();
// IME is an exceptional floating window that requires color view.
final boolean isImeWindow =
@@ -1164,7 +1164,7 @@
mWindow.mNavigationBarDividerColor, navBarSize,
navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge,
0 /* sideInset */, animate && !disallowAnimate,
- mForceWindowDrawsBarBackgrounds, controller);
+ mForceWindowDrawsBarBackgrounds, requestedVisibleTypes);
boolean oldDrawLegacy = mDrawLegacyNavigationBarBackground;
mDrawLegacyNavigationBarBackground =
(mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
@@ -1187,7 +1187,7 @@
updateColorViewInt(mStatusColorViewState, statusBarColor, 0,
mLastTopInset, false /* matchVertical */, statusBarNeedsLeftInset,
statusBarSideInset, animate && !disallowAnimate,
- mForceWindowDrawsBarBackgrounds, controller);
+ mForceWindowDrawsBarBackgrounds, requestedVisibleTypes);
if (mHasCaption) {
mDecorCaptionView.getCaption().setBackgroundColor(statusBarColor);
@@ -1206,7 +1206,7 @@
// Note: Once the app uses the R+ Window.setDecorFitsSystemWindows(false) API we no longer
// consume insets because they might no longer set SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION.
boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
- || !(controller == null || controller.isRequestedVisible(ITYPE_NAVIGATION_BAR));
+ || (requestedVisibleTypes & WindowInsets.Type.navigationBars()) == 0;
boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows;
boolean forceConsumingNavBar =
((mForceWindowDrawsBarBackgrounds || mDrawLegacyNavigationBarBackgroundHandled)
@@ -1226,10 +1226,10 @@
// If we didn't request fullscreen layout, but we still got it because of the
// mForceWindowDrawsBarBackgrounds flag, also consume top inset.
// If we should always consume system bars, only consume that if the app wanted to go to
- // fullscreen, as othrewise we can expect the app to handle it.
+ // fullscreen, as otherwise we can expect the app to handle it.
boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
|| (attrs.flags & FLAG_FULLSCREEN) != 0
- || !(controller == null || controller.isRequestedVisible(ITYPE_STATUS_BAR));
+ || (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0;
boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
&& decorFitsSystemWindows
&& (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
@@ -1438,10 +1438,10 @@
*/
private void updateColorViewInt(final ColorViewState state, int color, int dividerColor,
int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate,
- boolean force, WindowInsetsController controller) {
+ boolean force, @InsetsType int requestedVisibleTypes) {
state.present = state.attributes.isPresent(
- (controller.isRequestedVisible(state.attributes.insetsType)
- || mLastShouldAlwaysConsumeSystemBars),
+ (requestedVisibleTypes & state.attributes.insetsType) != 0
+ || mLastShouldAlwaysConsumeSystemBars,
mWindow.getAttributes().flags, force);
boolean show = state.attributes.isVisible(state.present, color,
mWindow.getAttributes().flags, force);
@@ -2686,11 +2686,10 @@
final int horizontalGravity;
final int seascapeGravity;
final String transitionName;
- final @InternalInsetsType int insetsType;
+ final @InsetsType int insetsType;
private ColorViewAttributes(int translucentFlag, int verticalGravity, int horizontalGravity,
- int seascapeGravity, String transitionName, int id,
- @InternalInsetsType int insetsType) {
+ int seascapeGravity, String transitionName, int id, @InsetsType int insetsType) {
this.id = id;
this.translucentFlag = translucentFlag;
this.verticalGravity = verticalGravity;
@@ -2707,13 +2706,14 @@
public boolean isVisible(boolean present, int color, int windowFlags, boolean force) {
return present
- && (color & Color.BLACK) != 0
- && ((windowFlags & translucentFlag) == 0 || force);
+ && Color.alpha(color) != 0
+ && ((windowFlags & translucentFlag) == 0 || force);
}
- public boolean isVisible(InsetsState state, int color, int windowFlags, boolean force) {
- final boolean present = isPresent(state.getSource(insetsType).isVisible(), windowFlags,
- force);
+ public boolean isVisible(@InsetsType int requestedVisibleTypes, int color, int windowFlags,
+ boolean force) {
+ final boolean requestedVisible = (requestedVisibleTypes & insetsType) != 0;
+ final boolean present = isPresent(requestedVisible, windowFlags, force);
return isVisible(present, color, windowFlags, force);
}
}
diff --git a/core/java/com/android/internal/util/ArtBinaryXmlPullParser.java b/core/java/com/android/internal/util/ArtBinaryXmlPullParser.java
new file mode 100644
index 0000000..c56bc49
--- /dev/null
+++ b/core/java/com/android/internal/util/ArtBinaryXmlPullParser.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.annotation.NonNull;
+
+import com.android.modules.utils.BinaryXmlPullParser;
+import com.android.modules.utils.FastDataInput;
+
+import java.io.DataInput;
+import java.io.InputStream;
+
+/**
+ * {@inheritDoc}
+ * <p>
+ * This decodes large code-points using 4-byte sequences, and <em>is not</em> compatible with the
+ * {@link DataInput} API contract, which specifies that large code-points must be encoded with
+ * 3-byte sequences.
+ */
+public class ArtBinaryXmlPullParser extends BinaryXmlPullParser {
+ @NonNull
+ protected FastDataInput obtainFastDataInput(@NonNull InputStream is) {
+ return ArtFastDataInput.obtain(is);
+ }
+}
diff --git a/core/java/com/android/internal/util/ArtBinaryXmlSerializer.java b/core/java/com/android/internal/util/ArtBinaryXmlSerializer.java
new file mode 100644
index 0000000..98a2135
--- /dev/null
+++ b/core/java/com/android/internal/util/ArtBinaryXmlSerializer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.annotation.NonNull;
+
+import com.android.modules.utils.BinaryXmlSerializer;
+import com.android.modules.utils.FastDataOutput;
+
+import java.io.DataOutput;
+import java.io.OutputStream;
+
+/**
+ * {@inheritDoc}
+ * <p>
+ * This encodes large code-points using 4-byte sequences and <em>is not</em> compatible with the
+ * {@link DataOutput} API contract, which specifies that large code-points must be encoded with
+ * 3-byte sequences.
+ */
+public class ArtBinaryXmlSerializer extends BinaryXmlSerializer {
+ @NonNull
+ @Override
+ protected FastDataOutput obtainFastDataOutput(@NonNull OutputStream os) {
+ return ArtFastDataOutput.obtain(os);
+ }
+}
diff --git a/core/java/com/android/internal/util/ArtFastDataInput.java b/core/java/com/android/internal/util/ArtFastDataInput.java
new file mode 100644
index 0000000..3e8916c
--- /dev/null
+++ b/core/java/com/android/internal/util/ArtFastDataInput.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.annotation.NonNull;
+import android.util.CharsetUtils;
+
+import com.android.modules.utils.FastDataInput;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * {@inheritDoc}
+ * <p>
+ * This decodes large code-points using 4-byte sequences, and <em>is not</em> compatible with the
+ * {@link DataInput} API contract, which specifies that large code-points must be encoded with
+ * 3-byte sequences.
+ */
+public class ArtFastDataInput extends FastDataInput {
+ private static AtomicReference<ArtFastDataInput> sInCache = new AtomicReference<>();
+
+ private final long mBufferPtr;
+
+ public ArtFastDataInput(@NonNull InputStream in, int bufferSize) {
+ super(in, bufferSize);
+
+ mBufferPtr = mRuntime.addressOf(mBuffer);
+ }
+
+ /**
+ * Obtain a {@link ArtFastDataInput} configured with the given
+ * {@link InputStream} and which decodes large code-points using 4-byte
+ * sequences.
+ * <p>
+ * This <em>is not</em> compatible with the {@link DataInput} API contract,
+ * which specifies that large code-points must be encoded with 3-byte
+ * sequences.
+ */
+ public static ArtFastDataInput obtain(@NonNull InputStream in) {
+ ArtFastDataInput instance = sInCache.getAndSet(null);
+ if (instance != null) {
+ instance.setInput(in);
+ return instance;
+ }
+ return new ArtFastDataInput(in, DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * Release a {@link ArtFastDataInput} to potentially be recycled. You must not
+ * interact with the object after releasing it.
+ */
+ public void release() {
+ super.release();
+
+ if (mBufferCap == DEFAULT_BUFFER_SIZE) {
+ // Try to return to the cache.
+ sInCache.compareAndSet(null, this);
+ }
+ }
+
+ @Override
+ public String readUTF() throws IOException {
+ // Attempt to read directly from buffer space if there's enough room,
+ // otherwise fall back to chunking into place
+ final int len = readUnsignedShort();
+ if (mBufferCap > len) {
+ if (mBufferLim - mBufferPos < len) fill(len);
+ final String res = CharsetUtils.fromModifiedUtf8Bytes(mBufferPtr, mBufferPos, len);
+ mBufferPos += len;
+ return res;
+ } else {
+ final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
+ readFully(tmp, 0, len);
+ return CharsetUtils.fromModifiedUtf8Bytes(mRuntime.addressOf(tmp), 0, len);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/util/ArtFastDataOutput.java b/core/java/com/android/internal/util/ArtFastDataOutput.java
new file mode 100644
index 0000000..ac595b6
--- /dev/null
+++ b/core/java/com/android/internal/util/ArtFastDataOutput.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.annotation.NonNull;
+import android.util.CharsetUtils;
+
+import com.android.modules.utils.FastDataOutput;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * {@inheritDoc}
+ * <p>
+ * This encodes large code-points using 4-byte sequences and <em>is not</em> compatible with the
+ * {@link DataOutput} API contract, which specifies that large code-points must be encoded with
+ * 3-byte sequences.
+ */
+public class ArtFastDataOutput extends FastDataOutput {
+ private static AtomicReference<ArtFastDataOutput> sOutCache = new AtomicReference<>();
+
+ private final long mBufferPtr;
+
+ public ArtFastDataOutput(@NonNull OutputStream out, int bufferSize) {
+ super(out, bufferSize);
+
+ mBufferPtr = mRuntime.addressOf(mBuffer);
+ }
+
+ /**
+ * Obtain an {@link ArtFastDataOutput} configured with the given
+ * {@link OutputStream} and which encodes large code-points using 4-byte
+ * sequences.
+ * <p>
+ * This <em>is not</em> compatible with the {@link DataOutput} API contract,
+ * which specifies that large code-points must be encoded with 3-byte
+ * sequences.
+ */
+ public static ArtFastDataOutput obtain(@NonNull OutputStream out) {
+ ArtFastDataOutput instance = sOutCache.getAndSet(null);
+ if (instance != null) {
+ instance.setOutput(out);
+ return instance;
+ }
+ return new ArtFastDataOutput(out, DEFAULT_BUFFER_SIZE);
+ }
+
+ @Override
+ public void release() {
+ super.release();
+
+ if (mBufferCap == DEFAULT_BUFFER_SIZE) {
+ // Try to return to the cache.
+ sOutCache.compareAndSet(null, this);
+ }
+ }
+
+ @Override
+ public void writeUTF(String s) throws IOException {
+ // Attempt to write directly to buffer space if there's enough room,
+ // otherwise fall back to chunking into place
+ if (mBufferCap - mBufferPos < 2 + s.length()) drain();
+
+ // Magnitude of this returned value indicates the number of bytes
+ // required to encode the string; sign indicates success/failure
+ int len = CharsetUtils.toModifiedUtf8Bytes(s, mBufferPtr, mBufferPos + 2, mBufferCap);
+ if (Math.abs(len) > MAX_UNSIGNED_SHORT) {
+ throw new IOException("Modified UTF-8 length too large: " + len);
+ }
+
+ if (len >= 0) {
+ // Positive value indicates the string was encoded into the buffer
+ // successfully, so we only need to prefix with length
+ writeShort(len);
+ mBufferPos += len;
+ } else {
+ // Negative value indicates buffer was too small and we need to
+ // allocate a temporary buffer for encoding
+ len = -len;
+ final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
+ CharsetUtils.toModifiedUtf8Bytes(s, mRuntime.addressOf(tmp), 0, tmp.length);
+ writeShort(len);
+ write(tmp, 0, len);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/util/BinaryXmlPullParser.java b/core/java/com/android/internal/util/BinaryXmlPullParser.java
deleted file mode 100644
index d3abac9..0000000
--- a/core/java/com/android/internal/util/BinaryXmlPullParser.java
+++ /dev/null
@@ -1,939 +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.internal.util;
-
-import static com.android.internal.util.BinaryXmlSerializer.ATTRIBUTE;
-import static com.android.internal.util.BinaryXmlSerializer.PROTOCOL_MAGIC_VERSION_0;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_BOOLEAN_FALSE;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_BOOLEAN_TRUE;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_BYTES_BASE64;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_BYTES_HEX;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_DOUBLE;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_FLOAT;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_INT;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_INT_HEX;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_LONG;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_LONG_HEX;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_NULL;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_STRING;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_STRING_INTERNED;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.Base64;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Parser that reads XML documents using a custom binary wire protocol which
- * benchmarking has shown to be 8.5x faster than {@link Xml.newFastPullParser()}
- * for a typical {@code packages.xml}.
- * <p>
- * The high-level design of the wire protocol is to directly serialize the event
- * stream, while efficiently and compactly writing strongly-typed primitives
- * delivered through the {@link TypedXmlSerializer} interface.
- * <p>
- * Each serialized event is a single byte where the lower half is a normal
- * {@link XmlPullParser} token and the upper half is an optional data type
- * signal, such as {@link #TYPE_INT}.
- * <p>
- * This parser has some specific limitations:
- * <ul>
- * <li>Only the UTF-8 encoding is supported.
- * <li>Variable length values, such as {@code byte[]} or {@link String}, are
- * limited to 65,535 bytes in length. Note that {@link String} values are stored
- * as UTF-8 on the wire.
- * <li>Namespaces, prefixes, properties, and options are unsupported.
- * </ul>
- */
-public final class BinaryXmlPullParser implements TypedXmlPullParser {
- private FastDataInput mIn;
-
- private int mCurrentToken = START_DOCUMENT;
- private int mCurrentDepth = 0;
- private String mCurrentName;
- private String mCurrentText;
-
- /**
- * Pool of attributes parsed for the currently tag. All interactions should
- * be done via {@link #obtainAttribute()}, {@link #findAttribute(String)},
- * and {@link #resetAttributes()}.
- */
- private int mAttributeCount = 0;
- private Attribute[] mAttributes;
-
- @Override
- public void setInput(InputStream is, String encoding) throws XmlPullParserException {
- if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) {
- throw new UnsupportedOperationException();
- }
-
- if (mIn != null) {
- mIn.release();
- mIn = null;
- }
-
- mIn = FastDataInput.obtainUsing4ByteSequences(is);
-
- mCurrentToken = START_DOCUMENT;
- mCurrentDepth = 0;
- mCurrentName = null;
- mCurrentText = null;
-
- mAttributeCount = 0;
- mAttributes = new Attribute[8];
- for (int i = 0; i < mAttributes.length; i++) {
- mAttributes[i] = new Attribute();
- }
-
- try {
- final byte[] magic = new byte[4];
- mIn.readFully(magic);
- if (!Arrays.equals(magic, PROTOCOL_MAGIC_VERSION_0)) {
- throw new IOException("Unexpected magic " + bytesToHexString(magic));
- }
-
- // We're willing to immediately consume a START_DOCUMENT if present,
- // but we're okay if it's missing
- if (peekNextExternalToken() == START_DOCUMENT) {
- consumeToken();
- }
- } catch (IOException e) {
- throw new XmlPullParserException(e.toString());
- }
- }
-
- @Override
- public void setInput(Reader in) throws XmlPullParserException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int next() throws XmlPullParserException, IOException {
- while (true) {
- final int token = nextToken();
- switch (token) {
- case START_TAG:
- case END_TAG:
- case END_DOCUMENT:
- return token;
- case TEXT:
- consumeAdditionalText();
- // Per interface docs, empty text regions are skipped
- if (mCurrentText == null || mCurrentText.length() == 0) {
- continue;
- } else {
- return TEXT;
- }
- }
- }
- }
-
- @Override
- public int nextToken() throws XmlPullParserException, IOException {
- if (mCurrentToken == XmlPullParser.END_TAG) {
- mCurrentDepth--;
- }
-
- int token;
- try {
- token = peekNextExternalToken();
- consumeToken();
- } catch (EOFException e) {
- token = END_DOCUMENT;
- }
- switch (token) {
- case XmlPullParser.START_TAG:
- // We need to peek forward to find the next external token so
- // that we parse all pending INTERNAL_ATTRIBUTE tokens
- peekNextExternalToken();
- mCurrentDepth++;
- break;
- }
- mCurrentToken = token;
- return token;
- }
-
- /**
- * Peek at the next "external" token without consuming it.
- * <p>
- * External tokens, such as {@link #START_TAG}, are expected by typical
- * {@link XmlPullParser} clients. In contrast, internal tokens, such as
- * {@link #ATTRIBUTE}, are not expected by typical clients.
- * <p>
- * This method consumes any internal events until it reaches the next
- * external event.
- */
- private int peekNextExternalToken() throws IOException, XmlPullParserException {
- while (true) {
- final int token = peekNextToken();
- switch (token) {
- case ATTRIBUTE:
- consumeToken();
- continue;
- default:
- return token;
- }
- }
- }
-
- /**
- * Peek at the next token in the underlying stream without consuming it.
- */
- private int peekNextToken() throws IOException {
- return mIn.peekByte() & 0x0f;
- }
-
- /**
- * Parse and consume the next token in the underlying stream.
- */
- private void consumeToken() throws IOException, XmlPullParserException {
- final int event = mIn.readByte();
- final int token = event & 0x0f;
- final int type = event & 0xf0;
- switch (token) {
- case ATTRIBUTE: {
- final Attribute attr = obtainAttribute();
- attr.name = mIn.readInternedUTF();
- attr.type = type;
- switch (type) {
- case TYPE_NULL:
- case TYPE_BOOLEAN_TRUE:
- case TYPE_BOOLEAN_FALSE:
- // Nothing extra to fill in
- break;
- case TYPE_STRING:
- attr.valueString = mIn.readUTF();
- break;
- case TYPE_STRING_INTERNED:
- attr.valueString = mIn.readInternedUTF();
- break;
- case TYPE_BYTES_HEX:
- case TYPE_BYTES_BASE64:
- final int len = mIn.readUnsignedShort();
- final byte[] res = new byte[len];
- mIn.readFully(res);
- attr.valueBytes = res;
- break;
- case TYPE_INT:
- case TYPE_INT_HEX:
- attr.valueInt = mIn.readInt();
- break;
- case TYPE_LONG:
- case TYPE_LONG_HEX:
- attr.valueLong = mIn.readLong();
- break;
- case TYPE_FLOAT:
- attr.valueFloat = mIn.readFloat();
- break;
- case TYPE_DOUBLE:
- attr.valueDouble = mIn.readDouble();
- break;
- default:
- throw new IOException("Unexpected data type " + type);
- }
- break;
- }
- case XmlPullParser.START_DOCUMENT: {
- mCurrentName = null;
- mCurrentText = null;
- if (mAttributeCount > 0) resetAttributes();
- break;
- }
- case XmlPullParser.END_DOCUMENT: {
- mCurrentName = null;
- mCurrentText = null;
- if (mAttributeCount > 0) resetAttributes();
- break;
- }
- case XmlPullParser.START_TAG: {
- mCurrentName = mIn.readInternedUTF();
- mCurrentText = null;
- if (mAttributeCount > 0) resetAttributes();
- break;
- }
- case XmlPullParser.END_TAG: {
- mCurrentName = mIn.readInternedUTF();
- mCurrentText = null;
- if (mAttributeCount > 0) resetAttributes();
- break;
- }
- case XmlPullParser.TEXT:
- case XmlPullParser.CDSECT:
- case XmlPullParser.PROCESSING_INSTRUCTION:
- case XmlPullParser.COMMENT:
- case XmlPullParser.DOCDECL:
- case XmlPullParser.IGNORABLE_WHITESPACE: {
- mCurrentName = null;
- mCurrentText = mIn.readUTF();
- if (mAttributeCount > 0) resetAttributes();
- break;
- }
- case XmlPullParser.ENTITY_REF: {
- mCurrentName = mIn.readUTF();
- mCurrentText = resolveEntity(mCurrentName);
- if (mAttributeCount > 0) resetAttributes();
- break;
- }
- default: {
- throw new IOException("Unknown token " + token + " with type " + type);
- }
- }
- }
-
- /**
- * When the current tag is {@link #TEXT}, consume all subsequent "text"
- * events, as described by {@link #next}. When finished, the current event
- * will still be {@link #TEXT}.
- */
- private void consumeAdditionalText() throws IOException, XmlPullParserException {
- String combinedText = mCurrentText;
- while (true) {
- final int token = peekNextExternalToken();
- switch (token) {
- case COMMENT:
- case PROCESSING_INSTRUCTION:
- // Quietly consumed
- consumeToken();
- break;
- case TEXT:
- case CDSECT:
- case ENTITY_REF:
- // Additional text regions collected
- consumeToken();
- combinedText += mCurrentText;
- break;
- default:
- // Next token is something non-text, so wrap things up
- mCurrentToken = TEXT;
- mCurrentName = null;
- mCurrentText = combinedText;
- return;
- }
- }
- }
-
- static @NonNull String resolveEntity(@NonNull String entity)
- throws XmlPullParserException {
- switch (entity) {
- case "lt": return "<";
- case "gt": return ">";
- case "amp": return "&";
- case "apos": return "'";
- case "quot": return "\"";
- }
- if (entity.length() > 1 && entity.charAt(0) == '#') {
- final char c = (char) Integer.parseInt(entity.substring(1));
- return new String(new char[] { c });
- }
- throw new XmlPullParserException("Unknown entity " + entity);
- }
-
- @Override
- public void require(int type, String namespace, String name)
- throws XmlPullParserException, IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- if (mCurrentToken != type || !Objects.equals(mCurrentName, name)) {
- throw new XmlPullParserException(getPositionDescription());
- }
- }
-
- @Override
- public String nextText() throws XmlPullParserException, IOException {
- if (getEventType() != START_TAG) {
- throw new XmlPullParserException(getPositionDescription());
- }
- int eventType = next();
- if (eventType == TEXT) {
- String result = getText();
- eventType = next();
- if (eventType != END_TAG) {
- throw new XmlPullParserException(getPositionDescription());
- }
- return result;
- } else if (eventType == END_TAG) {
- return "";
- } else {
- throw new XmlPullParserException(getPositionDescription());
- }
- }
-
- @Override
- public int nextTag() throws XmlPullParserException, IOException {
- int eventType = next();
- if (eventType == TEXT && isWhitespace()) {
- eventType = next();
- }
- if (eventType != START_TAG && eventType != END_TAG) {
- throw new XmlPullParserException(getPositionDescription());
- }
- return eventType;
- }
-
- /**
- * Allocate and return a new {@link Attribute} associated with the tag being
- * currently processed. This will automatically grow the internal pool as
- * needed.
- */
- private @NonNull Attribute obtainAttribute() {
- if (mAttributeCount == mAttributes.length) {
- final int before = mAttributes.length;
- final int after = before + (before >> 1);
- mAttributes = Arrays.copyOf(mAttributes, after);
- for (int i = before; i < after; i++) {
- mAttributes[i] = new Attribute();
- }
- }
- return mAttributes[mAttributeCount++];
- }
-
- /**
- * Clear any {@link Attribute} instances that have been allocated by
- * {@link #obtainAttribute()}, returning them into the pool for recycling.
- */
- private void resetAttributes() {
- for (int i = 0; i < mAttributeCount; i++) {
- mAttributes[i].reset();
- }
- mAttributeCount = 0;
- }
-
- @Override
- public int getAttributeIndex(String namespace, String name) {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- for (int i = 0; i < mAttributeCount; i++) {
- if (Objects.equals(mAttributes[i].name, name)) {
- return i;
- }
- }
- return -1;
- }
-
- @Override
- public String getAttributeValue(String namespace, String name) {
- final int index = getAttributeIndex(namespace, name);
- if (index != -1) {
- return mAttributes[index].getValueString();
- } else {
- return null;
- }
- }
-
- @Override
- public String getAttributeValue(int index) {
- return mAttributes[index].getValueString();
- }
-
- @Override
- public byte[] getAttributeBytesHex(int index) throws XmlPullParserException {
- return mAttributes[index].getValueBytesHex();
- }
-
- @Override
- public byte[] getAttributeBytesBase64(int index) throws XmlPullParserException {
- return mAttributes[index].getValueBytesBase64();
- }
-
- @Override
- public int getAttributeInt(int index) throws XmlPullParserException {
- return mAttributes[index].getValueInt();
- }
-
- @Override
- public int getAttributeIntHex(int index) throws XmlPullParserException {
- return mAttributes[index].getValueIntHex();
- }
-
- @Override
- public long getAttributeLong(int index) throws XmlPullParserException {
- return mAttributes[index].getValueLong();
- }
-
- @Override
- public long getAttributeLongHex(int index) throws XmlPullParserException {
- return mAttributes[index].getValueLongHex();
- }
-
- @Override
- public float getAttributeFloat(int index) throws XmlPullParserException {
- return mAttributes[index].getValueFloat();
- }
-
- @Override
- public double getAttributeDouble(int index) throws XmlPullParserException {
- return mAttributes[index].getValueDouble();
- }
-
- @Override
- public boolean getAttributeBoolean(int index) throws XmlPullParserException {
- return mAttributes[index].getValueBoolean();
- }
-
- @Override
- public String getText() {
- return mCurrentText;
- }
-
- @Override
- public char[] getTextCharacters(int[] holderForStartAndLength) {
- final char[] chars = mCurrentText.toCharArray();
- holderForStartAndLength[0] = 0;
- holderForStartAndLength[1] = chars.length;
- return chars;
- }
-
- @Override
- public String getInputEncoding() {
- return StandardCharsets.UTF_8.name();
- }
-
- @Override
- public int getDepth() {
- return mCurrentDepth;
- }
-
- @Override
- public String getPositionDescription() {
- // Not very helpful, but it's the best information we have
- return "Token " + mCurrentToken + " at depth " + mCurrentDepth;
- }
-
- @Override
- public int getLineNumber() {
- return -1;
- }
-
- @Override
- public int getColumnNumber() {
- return -1;
- }
-
- @Override
- public boolean isWhitespace() throws XmlPullParserException {
- switch (mCurrentToken) {
- case IGNORABLE_WHITESPACE:
- return true;
- case TEXT:
- case CDSECT:
- return !TextUtils.isGraphic(mCurrentText);
- default:
- throw new XmlPullParserException("Not applicable for token " + mCurrentToken);
- }
- }
-
- @Override
- public String getNamespace() {
- switch (mCurrentToken) {
- case START_TAG:
- case END_TAG:
- // Namespaces are unsupported
- return NO_NAMESPACE;
- default:
- return null;
- }
- }
-
- @Override
- public String getName() {
- return mCurrentName;
- }
-
- @Override
- public String getPrefix() {
- // Prefixes are not supported
- return null;
- }
-
- @Override
- public boolean isEmptyElementTag() throws XmlPullParserException {
- switch (mCurrentToken) {
- case START_TAG:
- try {
- return (peekNextExternalToken() == END_TAG);
- } catch (IOException e) {
- throw new XmlPullParserException(e.toString());
- }
- default:
- throw new XmlPullParserException("Not at START_TAG");
- }
- }
-
- @Override
- public int getAttributeCount() {
- return mAttributeCount;
- }
-
- @Override
- public String getAttributeNamespace(int index) {
- // Namespaces are unsupported
- return NO_NAMESPACE;
- }
-
- @Override
- public String getAttributeName(int index) {
- return mAttributes[index].name;
- }
-
- @Override
- public String getAttributePrefix(int index) {
- // Prefixes are not supported
- return null;
- }
-
- @Override
- public String getAttributeType(int index) {
- // Validation is not supported
- return "CDATA";
- }
-
- @Override
- public boolean isAttributeDefault(int index) {
- // Validation is not supported
- return false;
- }
-
- @Override
- public int getEventType() throws XmlPullParserException {
- return mCurrentToken;
- }
-
- @Override
- public int getNamespaceCount(int depth) throws XmlPullParserException {
- // Namespaces are unsupported
- return 0;
- }
-
- @Override
- public String getNamespacePrefix(int pos) throws XmlPullParserException {
- // Namespaces are unsupported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getNamespaceUri(int pos) throws XmlPullParserException {
- // Namespaces are unsupported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getNamespace(String prefix) {
- // Namespaces are unsupported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void defineEntityReplacementText(String entityName, String replacementText)
- throws XmlPullParserException {
- // Custom entities are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setFeature(String name, boolean state) throws XmlPullParserException {
- // Features are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean getFeature(String name) {
- // Features are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setProperty(String name, Object value) throws XmlPullParserException {
- // Properties are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Object getProperty(String name) {
- // Properties are not supported
- throw new UnsupportedOperationException();
- }
-
- private static IllegalArgumentException illegalNamespace() {
- throw new IllegalArgumentException("Namespaces are not supported");
- }
-
- /**
- * Holder representing a single attribute. This design enables object
- * recycling without resorting to autoboxing.
- * <p>
- * To support conversion between human-readable XML and binary XML, the
- * various accessor methods will transparently convert from/to
- * human-readable values when needed.
- */
- private static class Attribute {
- public String name;
- public int type;
-
- public String valueString;
- public byte[] valueBytes;
- public int valueInt;
- public long valueLong;
- public float valueFloat;
- public double valueDouble;
-
- public void reset() {
- name = null;
- valueString = null;
- valueBytes = null;
- }
-
- public @Nullable String getValueString() {
- switch (type) {
- case TYPE_NULL:
- return null;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- return valueString;
- case TYPE_BYTES_HEX:
- return bytesToHexString(valueBytes);
- case TYPE_BYTES_BASE64:
- return Base64.encodeToString(valueBytes, Base64.NO_WRAP);
- case TYPE_INT:
- return Integer.toString(valueInt);
- case TYPE_INT_HEX:
- return Integer.toString(valueInt, 16);
- case TYPE_LONG:
- return Long.toString(valueLong);
- case TYPE_LONG_HEX:
- return Long.toString(valueLong, 16);
- case TYPE_FLOAT:
- return Float.toString(valueFloat);
- case TYPE_DOUBLE:
- return Double.toString(valueDouble);
- case TYPE_BOOLEAN_TRUE:
- return "true";
- case TYPE_BOOLEAN_FALSE:
- return "false";
- default:
- // Unknown data type; null is the best we can offer
- return null;
- }
- }
-
- public @Nullable byte[] getValueBytesHex() throws XmlPullParserException {
- switch (type) {
- case TYPE_NULL:
- return null;
- case TYPE_BYTES_HEX:
- case TYPE_BYTES_BASE64:
- return valueBytes;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return hexStringToBytes(valueString);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public @Nullable byte[] getValueBytesBase64() throws XmlPullParserException {
- switch (type) {
- case TYPE_NULL:
- return null;
- case TYPE_BYTES_HEX:
- case TYPE_BYTES_BASE64:
- return valueBytes;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Base64.decode(valueString, Base64.NO_WRAP);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public int getValueInt() throws XmlPullParserException {
- switch (type) {
- case TYPE_INT:
- case TYPE_INT_HEX:
- return valueInt;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Integer.parseInt(valueString);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public int getValueIntHex() throws XmlPullParserException {
- switch (type) {
- case TYPE_INT:
- case TYPE_INT_HEX:
- return valueInt;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Integer.parseInt(valueString, 16);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public long getValueLong() throws XmlPullParserException {
- switch (type) {
- case TYPE_LONG:
- case TYPE_LONG_HEX:
- return valueLong;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Long.parseLong(valueString);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public long getValueLongHex() throws XmlPullParserException {
- switch (type) {
- case TYPE_LONG:
- case TYPE_LONG_HEX:
- return valueLong;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Long.parseLong(valueString, 16);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public float getValueFloat() throws XmlPullParserException {
- switch (type) {
- case TYPE_FLOAT:
- return valueFloat;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Float.parseFloat(valueString);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public double getValueDouble() throws XmlPullParserException {
- switch (type) {
- case TYPE_DOUBLE:
- return valueDouble;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Double.parseDouble(valueString);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public boolean getValueBoolean() throws XmlPullParserException {
- switch (type) {
- case TYPE_BOOLEAN_TRUE:
- return true;
- case TYPE_BOOLEAN_FALSE:
- return false;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- if ("true".equalsIgnoreCase(valueString)) {
- return true;
- } else if ("false".equalsIgnoreCase(valueString)) {
- return false;
- } else {
- throw new XmlPullParserException(
- "Invalid attribute " + name + ": " + valueString);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
- }
-
- // NOTE: To support unbundled clients, we include an inlined copy
- // of hex conversion logic from HexDump below
- private final static char[] HEX_DIGITS =
- { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
-
- private static int toByte(char c) {
- if (c >= '0' && c <= '9') return (c - '0');
- if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
- if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
- throw new IllegalArgumentException("Invalid hex char '" + c + "'");
- }
-
- static String bytesToHexString(byte[] value) {
- final int length = value.length;
- final char[] buf = new char[length * 2];
- int bufIndex = 0;
- for (int i = 0; i < length; i++) {
- byte b = value[i];
- buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
- buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
- }
- return new String(buf);
- }
-
- static byte[] hexStringToBytes(String value) {
- final int length = value.length();
- if (length % 2 != 0) {
- throw new IllegalArgumentException("Invalid hex length " + length);
- }
- byte[] buffer = new byte[length / 2];
- for (int i = 0; i < length; i += 2) {
- buffer[i / 2] = (byte) ((toByte(value.charAt(i)) << 4)
- | toByte(value.charAt(i + 1)));
- }
- return buffer;
- }
-}
diff --git a/core/java/com/android/internal/util/BinaryXmlSerializer.java b/core/java/com/android/internal/util/BinaryXmlSerializer.java
deleted file mode 100644
index 485430a..0000000
--- a/core/java/com/android/internal/util/BinaryXmlSerializer.java
+++ /dev/null
@@ -1,398 +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.internal.util;
-
-import static org.xmlpull.v1.XmlPullParser.CDSECT;
-import static org.xmlpull.v1.XmlPullParser.COMMENT;
-import static org.xmlpull.v1.XmlPullParser.DOCDECL;
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.END_TAG;
-import static org.xmlpull.v1.XmlPullParser.ENTITY_REF;
-import static org.xmlpull.v1.XmlPullParser.IGNORABLE_WHITESPACE;
-import static org.xmlpull.v1.XmlPullParser.PROCESSING_INSTRUCTION;
-import static org.xmlpull.v1.XmlPullParser.START_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.START_TAG;
-import static org.xmlpull.v1.XmlPullParser.TEXT;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.Writer;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-/**
- * Serializer that writes XML documents using a custom binary wire protocol
- * which benchmarking has shown to be 4.3x faster and use 2.4x less disk space
- * than {@code Xml.newFastSerializer()} for a typical {@code packages.xml}.
- * <p>
- * The high-level design of the wire protocol is to directly serialize the event
- * stream, while efficiently and compactly writing strongly-typed primitives
- * delivered through the {@link TypedXmlSerializer} interface.
- * <p>
- * Each serialized event is a single byte where the lower half is a normal
- * {@link XmlPullParser} token and the upper half is an optional data type
- * signal, such as {@link #TYPE_INT}.
- * <p>
- * This serializer has some specific limitations:
- * <ul>
- * <li>Only the UTF-8 encoding is supported.
- * <li>Variable length values, such as {@code byte[]} or {@link String}, are
- * limited to 65,535 bytes in length. Note that {@link String} values are stored
- * as UTF-8 on the wire.
- * <li>Namespaces, prefixes, properties, and options are unsupported.
- * </ul>
- */
-public final class BinaryXmlSerializer implements TypedXmlSerializer {
- /**
- * The wire protocol always begins with a well-known magic value of
- * {@code ABX_}, representing "Android Binary XML." The final byte is a
- * version number which may be incremented as the protocol changes.
- */
- public static final byte[] PROTOCOL_MAGIC_VERSION_0 = new byte[] { 0x41, 0x42, 0x58, 0x00 };
-
- /**
- * Internal token which represents an attribute associated with the most
- * recent {@link #START_TAG} token.
- */
- static final int ATTRIBUTE = 15;
-
- static final int TYPE_NULL = 1 << 4;
- static final int TYPE_STRING = 2 << 4;
- static final int TYPE_STRING_INTERNED = 3 << 4;
- static final int TYPE_BYTES_HEX = 4 << 4;
- static final int TYPE_BYTES_BASE64 = 5 << 4;
- static final int TYPE_INT = 6 << 4;
- static final int TYPE_INT_HEX = 7 << 4;
- static final int TYPE_LONG = 8 << 4;
- static final int TYPE_LONG_HEX = 9 << 4;
- static final int TYPE_FLOAT = 10 << 4;
- static final int TYPE_DOUBLE = 11 << 4;
- static final int TYPE_BOOLEAN_TRUE = 12 << 4;
- static final int TYPE_BOOLEAN_FALSE = 13 << 4;
-
- private FastDataOutput mOut;
-
- /**
- * Stack of tags which are currently active via {@link #startTag} and which
- * haven't been terminated via {@link #endTag}.
- */
- private int mTagCount = 0;
- private String[] mTagNames;
-
- /**
- * Write the given token and optional {@link String} into our buffer.
- */
- private void writeToken(int token, @Nullable String text) throws IOException {
- if (text != null) {
- mOut.writeByte(token | TYPE_STRING);
- mOut.writeUTF(text);
- } else {
- mOut.writeByte(token | TYPE_NULL);
- }
- }
-
- @Override
- public void setOutput(@NonNull OutputStream os, @Nullable String encoding) throws IOException {
- if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) {
- throw new UnsupportedOperationException();
- }
-
- mOut = FastDataOutput.obtainUsing4ByteSequences(os);
- mOut.write(PROTOCOL_MAGIC_VERSION_0);
-
- mTagCount = 0;
- mTagNames = new String[8];
- }
-
- @Override
- public void setOutput(Writer writer) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void flush() throws IOException {
- if (mOut != null) {
- mOut.flush();
- }
- }
-
- @Override
- public void startDocument(@Nullable String encoding, @Nullable Boolean standalone)
- throws IOException {
- if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) {
- throw new UnsupportedOperationException();
- }
- if (standalone != null && !standalone) {
- throw new UnsupportedOperationException();
- }
- mOut.writeByte(START_DOCUMENT | TYPE_NULL);
- }
-
- @Override
- public void endDocument() throws IOException {
- mOut.writeByte(END_DOCUMENT | TYPE_NULL);
- flush();
-
- mOut.release();
- mOut = null;
- }
-
- @Override
- public int getDepth() {
- return mTagCount;
- }
-
- @Override
- public String getNamespace() {
- // Namespaces are unsupported
- return XmlPullParser.NO_NAMESPACE;
- }
-
- @Override
- public String getName() {
- return mTagNames[mTagCount - 1];
- }
-
- @Override
- public XmlSerializer startTag(String namespace, String name) throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- if (mTagCount == mTagNames.length) {
- mTagNames = Arrays.copyOf(mTagNames, mTagCount + (mTagCount >> 1));
- }
- mTagNames[mTagCount++] = name;
- mOut.writeByte(START_TAG | TYPE_STRING_INTERNED);
- mOut.writeInternedUTF(name);
- return this;
- }
-
- @Override
- public XmlSerializer endTag(String namespace, String name) throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mTagCount--;
- mOut.writeByte(END_TAG | TYPE_STRING_INTERNED);
- mOut.writeInternedUTF(name);
- return this;
- }
-
- @Override
- public XmlSerializer attribute(String namespace, String name, String value) throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_STRING);
- mOut.writeInternedUTF(name);
- mOut.writeUTF(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeInterned(String namespace, String name, String value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_STRING_INTERNED);
- mOut.writeInternedUTF(name);
- mOut.writeInternedUTF(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeBytesHex(String namespace, String name, byte[] value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_BYTES_HEX);
- mOut.writeInternedUTF(name);
- mOut.writeShort(value.length);
- mOut.write(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeBytesBase64(String namespace, String name, byte[] value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_BYTES_BASE64);
- mOut.writeInternedUTF(name);
- mOut.writeShort(value.length);
- mOut.write(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeInt(String namespace, String name, int value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_INT);
- mOut.writeInternedUTF(name);
- mOut.writeInt(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeIntHex(String namespace, String name, int value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_INT_HEX);
- mOut.writeInternedUTF(name);
- mOut.writeInt(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeLong(String namespace, String name, long value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_LONG);
- mOut.writeInternedUTF(name);
- mOut.writeLong(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeLongHex(String namespace, String name, long value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_LONG_HEX);
- mOut.writeInternedUTF(name);
- mOut.writeLong(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeFloat(String namespace, String name, float value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_FLOAT);
- mOut.writeInternedUTF(name);
- mOut.writeFloat(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeDouble(String namespace, String name, double value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_DOUBLE);
- mOut.writeInternedUTF(name);
- mOut.writeDouble(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeBoolean(String namespace, String name, boolean value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- if (value) {
- mOut.writeByte(ATTRIBUTE | TYPE_BOOLEAN_TRUE);
- mOut.writeInternedUTF(name);
- } else {
- mOut.writeByte(ATTRIBUTE | TYPE_BOOLEAN_FALSE);
- mOut.writeInternedUTF(name);
- }
- return this;
- }
-
- @Override
- public XmlSerializer text(char[] buf, int start, int len) throws IOException {
- writeToken(TEXT, new String(buf, start, len));
- return this;
- }
-
- @Override
- public XmlSerializer text(String text) throws IOException {
- writeToken(TEXT, text);
- return this;
- }
-
- @Override
- public void cdsect(String text) throws IOException {
- writeToken(CDSECT, text);
- }
-
- @Override
- public void entityRef(String text) throws IOException {
- writeToken(ENTITY_REF, text);
- }
-
- @Override
- public void processingInstruction(String text) throws IOException {
- writeToken(PROCESSING_INSTRUCTION, text);
- }
-
- @Override
- public void comment(String text) throws IOException {
- writeToken(COMMENT, text);
- }
-
- @Override
- public void docdecl(String text) throws IOException {
- writeToken(DOCDECL, text);
- }
-
- @Override
- public void ignorableWhitespace(String text) throws IOException {
- writeToken(IGNORABLE_WHITESPACE, text);
- }
-
- @Override
- public void setFeature(String name, boolean state) {
- // Quietly handle no-op features
- if ("http://xmlpull.org/v1/doc/features.html#indent-output".equals(name)) {
- return;
- }
- // Features are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean getFeature(String name) {
- // Features are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setProperty(String name, Object value) {
- // Properties are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Object getProperty(String name) {
- // Properties are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setPrefix(String prefix, String namespace) {
- // Prefixes are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getPrefix(String namespace, boolean generatePrefix) {
- // Prefixes are not supported
- throw new UnsupportedOperationException();
- }
-
- private static IllegalArgumentException illegalNamespace() {
- throw new IllegalArgumentException("Namespaces are not supported");
- }
-}
diff --git a/core/java/com/android/internal/util/FastDataInput.java b/core/java/com/android/internal/util/FastDataInput.java
deleted file mode 100644
index 5117034..0000000
--- a/core/java/com/android/internal/util/FastDataInput.java
+++ /dev/null
@@ -1,362 +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.internal.util;
-
-import android.annotation.NonNull;
-import android.util.CharsetUtils;
-
-import dalvik.system.VMRuntime;
-
-import java.io.BufferedInputStream;
-import java.io.Closeable;
-import java.io.DataInput;
-import java.io.DataInputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Optimized implementation of {@link DataInput} which buffers data in memory
- * from the underlying {@link InputStream}.
- * <p>
- * Benchmarks have demonstrated this class is 3x more efficient than using a
- * {@link DataInputStream} with a {@link BufferedInputStream}.
- */
-public class FastDataInput implements DataInput, Closeable {
- private static final int MAX_UNSIGNED_SHORT = 65_535;
-
- private static final int DEFAULT_BUFFER_SIZE = 32_768;
-
- private static AtomicReference<FastDataInput> sInCache = new AtomicReference<>();
-
- private final VMRuntime mRuntime;
-
- private final byte[] mBuffer;
- private final long mBufferPtr;
- private final int mBufferCap;
- private final boolean mUse4ByteSequence;
-
- private InputStream mIn;
- private int mBufferPos;
- private int mBufferLim;
-
- /**
- * Values that have been "interned" by {@link #readInternedUTF()}.
- */
- private int mStringRefCount = 0;
- private String[] mStringRefs = new String[32];
-
- /**
- * @deprecated callers must specify {@code use4ByteSequence} so they make a
- * clear choice about working around a long-standing ART bug, as
- * described by the {@code kUtfUse4ByteSequence} comments in
- * {@code art/runtime/jni/jni_internal.cc}.
- */
- @Deprecated
- public FastDataInput(@NonNull InputStream in, int bufferSize) {
- this(in, bufferSize, true /* use4ByteSequence */);
- }
-
- public FastDataInput(@NonNull InputStream in, int bufferSize, boolean use4ByteSequence) {
- mRuntime = VMRuntime.getRuntime();
- mIn = Objects.requireNonNull(in);
- if (bufferSize < 8) {
- throw new IllegalArgumentException();
- }
-
- mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
- mBufferPtr = mRuntime.addressOf(mBuffer);
- mBufferCap = mBuffer.length;
- mUse4ByteSequence = use4ByteSequence;
- }
-
- /**
- * Obtain a {@link FastDataInput} configured with the given
- * {@link InputStream} and which encodes large code-points using 3-byte
- * sequences.
- * <p>
- * This <em>is</em> compatible with the {@link DataInput} API contract,
- * which specifies that large code-points must be encoded with 3-byte
- * sequences.
- */
- public static FastDataInput obtainUsing3ByteSequences(@NonNull InputStream in) {
- return new FastDataInput(in, DEFAULT_BUFFER_SIZE, false /* use4ByteSequence */);
- }
-
- /**
- * Obtain a {@link FastDataInput} configured with the given
- * {@link InputStream} and which decodes large code-points using 4-byte
- * sequences.
- * <p>
- * This <em>is not</em> compatible with the {@link DataInput} API contract,
- * which specifies that large code-points must be encoded with 3-byte
- * sequences.
- */
- public static FastDataInput obtainUsing4ByteSequences(@NonNull InputStream in) {
- FastDataInput instance = sInCache.getAndSet(null);
- if (instance != null) {
- instance.setInput(in);
- return instance;
- }
- return new FastDataInput(in, DEFAULT_BUFFER_SIZE, true /* use4ByteSequence */);
- }
-
- /**
- * Release a {@link FastDataInput} to potentially be recycled. You must not
- * interact with the object after releasing it.
- */
- public void release() {
- mIn = null;
- mBufferPos = 0;
- mBufferLim = 0;
- mStringRefCount = 0;
-
- if (mBufferCap == DEFAULT_BUFFER_SIZE && mUse4ByteSequence) {
- // Try to return to the cache.
- sInCache.compareAndSet(null, this);
- }
- }
-
- /**
- * Re-initializes the object for the new input.
- */
- private void setInput(@NonNull InputStream in) {
- mIn = Objects.requireNonNull(in);
- mBufferPos = 0;
- mBufferLim = 0;
- mStringRefCount = 0;
- }
-
- private void fill(int need) throws IOException {
- final int remain = mBufferLim - mBufferPos;
- System.arraycopy(mBuffer, mBufferPos, mBuffer, 0, remain);
- mBufferPos = 0;
- mBufferLim = remain;
- need -= remain;
-
- while (need > 0) {
- int c = mIn.read(mBuffer, mBufferLim, mBufferCap - mBufferLim);
- if (c == -1) {
- throw new EOFException();
- } else {
- mBufferLim += c;
- need -= c;
- }
- }
- }
-
- @Override
- public void close() throws IOException {
- mIn.close();
- release();
- }
-
- @Override
- public void readFully(byte[] b) throws IOException {
- readFully(b, 0, b.length);
- }
-
- @Override
- public void readFully(byte[] b, int off, int len) throws IOException {
- // Attempt to read directly from buffer space if there's enough room,
- // otherwise fall back to chunking into place
- if (mBufferCap >= len) {
- if (mBufferLim - mBufferPos < len) fill(len);
- System.arraycopy(mBuffer, mBufferPos, b, off, len);
- mBufferPos += len;
- } else {
- final int remain = mBufferLim - mBufferPos;
- System.arraycopy(mBuffer, mBufferPos, b, off, remain);
- mBufferPos += remain;
- off += remain;
- len -= remain;
-
- while (len > 0) {
- int c = mIn.read(b, off, len);
- if (c == -1) {
- throw new EOFException();
- } else {
- off += c;
- len -= c;
- }
- }
- }
- }
-
- @Override
- public String readUTF() throws IOException {
- if (mUse4ByteSequence) {
- return readUTFUsing4ByteSequences();
- } else {
- return readUTFUsing3ByteSequences();
- }
- }
-
- private String readUTFUsing4ByteSequences() throws IOException {
- // Attempt to read directly from buffer space if there's enough room,
- // otherwise fall back to chunking into place
- final int len = readUnsignedShort();
- if (mBufferCap > len) {
- if (mBufferLim - mBufferPos < len) fill(len);
- final String res = CharsetUtils.fromModifiedUtf8Bytes(mBufferPtr, mBufferPos, len);
- mBufferPos += len;
- return res;
- } else {
- final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
- readFully(tmp, 0, len);
- return CharsetUtils.fromModifiedUtf8Bytes(mRuntime.addressOf(tmp), 0, len);
- }
- }
-
- private String readUTFUsing3ByteSequences() throws IOException {
- // Attempt to read directly from buffer space if there's enough room,
- // otherwise fall back to chunking into place
- final int len = readUnsignedShort();
- if (mBufferCap > len) {
- if (mBufferLim - mBufferPos < len) fill(len);
- final String res = ModifiedUtf8.decode(mBuffer, new char[len], mBufferPos, len);
- mBufferPos += len;
- return res;
- } else {
- final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
- readFully(tmp, 0, len);
- return ModifiedUtf8.decode(tmp, new char[len], 0, len);
- }
- }
-
- /**
- * Read a {@link String} value with the additional signal that the given
- * value is a candidate for being canonicalized, similar to
- * {@link String#intern()}.
- * <p>
- * Canonicalization is implemented by writing each unique string value once
- * the first time it appears, and then writing a lightweight {@code short}
- * reference when that string is written again in the future.
- *
- * @see FastDataOutput#writeInternedUTF(String)
- */
- public @NonNull String readInternedUTF() throws IOException {
- final int ref = readUnsignedShort();
- if (ref == MAX_UNSIGNED_SHORT) {
- final String s = readUTF();
-
- // We can only safely intern when we have remaining values; if we're
- // full we at least sent the string value above
- if (mStringRefCount < MAX_UNSIGNED_SHORT) {
- if (mStringRefCount == mStringRefs.length) {
- mStringRefs = Arrays.copyOf(mStringRefs,
- mStringRefCount + (mStringRefCount >> 1));
- }
- mStringRefs[mStringRefCount++] = s;
- }
-
- return s;
- } else {
- return mStringRefs[ref];
- }
- }
-
- @Override
- public boolean readBoolean() throws IOException {
- return readByte() != 0;
- }
-
- /**
- * Returns the same decoded value as {@link #readByte()} but without
- * actually consuming the underlying data.
- */
- public byte peekByte() throws IOException {
- if (mBufferLim - mBufferPos < 1) fill(1);
- return mBuffer[mBufferPos];
- }
-
- @Override
- public byte readByte() throws IOException {
- if (mBufferLim - mBufferPos < 1) fill(1);
- return mBuffer[mBufferPos++];
- }
-
- @Override
- public int readUnsignedByte() throws IOException {
- return Byte.toUnsignedInt(readByte());
- }
-
- @Override
- public short readShort() throws IOException {
- if (mBufferLim - mBufferPos < 2) fill(2);
- return (short) (((mBuffer[mBufferPos++] & 0xff) << 8) |
- ((mBuffer[mBufferPos++] & 0xff) << 0));
- }
-
- @Override
- public int readUnsignedShort() throws IOException {
- return Short.toUnsignedInt((short) readShort());
- }
-
- @Override
- public char readChar() throws IOException {
- return (char) readShort();
- }
-
- @Override
- public int readInt() throws IOException {
- if (mBufferLim - mBufferPos < 4) fill(4);
- return (((mBuffer[mBufferPos++] & 0xff) << 24) |
- ((mBuffer[mBufferPos++] & 0xff) << 16) |
- ((mBuffer[mBufferPos++] & 0xff) << 8) |
- ((mBuffer[mBufferPos++] & 0xff) << 0));
- }
-
- @Override
- public long readLong() throws IOException {
- if (mBufferLim - mBufferPos < 8) fill(8);
- int h = ((mBuffer[mBufferPos++] & 0xff) << 24) |
- ((mBuffer[mBufferPos++] & 0xff) << 16) |
- ((mBuffer[mBufferPos++] & 0xff) << 8) |
- ((mBuffer[mBufferPos++] & 0xff) << 0);
- int l = ((mBuffer[mBufferPos++] & 0xff) << 24) |
- ((mBuffer[mBufferPos++] & 0xff) << 16) |
- ((mBuffer[mBufferPos++] & 0xff) << 8) |
- ((mBuffer[mBufferPos++] & 0xff) << 0);
- return (((long) h) << 32L) | ((long) l) & 0xffffffffL;
- }
-
- @Override
- public float readFloat() throws IOException {
- return Float.intBitsToFloat(readInt());
- }
-
- @Override
- public double readDouble() throws IOException {
- return Double.longBitsToDouble(readLong());
- }
-
- @Override
- public int skipBytes(int n) throws IOException {
- // Callers should read data piecemeal
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String readLine() throws IOException {
- // Callers should read data piecemeal
- throw new UnsupportedOperationException();
- }
-}
diff --git a/core/java/com/android/internal/util/FastDataOutput.java b/core/java/com/android/internal/util/FastDataOutput.java
deleted file mode 100644
index 5b6075e..0000000
--- a/core/java/com/android/internal/util/FastDataOutput.java
+++ /dev/null
@@ -1,343 +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.internal.util;
-
-import android.annotation.NonNull;
-import android.util.CharsetUtils;
-
-import dalvik.system.VMRuntime;
-
-import java.io.BufferedOutputStream;
-import java.io.Closeable;
-import java.io.DataOutput;
-import java.io.DataOutputStream;
-import java.io.Flushable;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.HashMap;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Optimized implementation of {@link DataOutput} which buffers data in memory
- * before flushing to the underlying {@link OutputStream}.
- * <p>
- * Benchmarks have demonstrated this class is 2x more efficient than using a
- * {@link DataOutputStream} with a {@link BufferedOutputStream}.
- */
-public class FastDataOutput implements DataOutput, Flushable, Closeable {
- private static final int MAX_UNSIGNED_SHORT = 65_535;
-
- private static final int DEFAULT_BUFFER_SIZE = 32_768;
-
- private static AtomicReference<FastDataOutput> sOutCache = new AtomicReference<>();
-
- private final VMRuntime mRuntime;
-
- private final byte[] mBuffer;
- private final long mBufferPtr;
- private final int mBufferCap;
- private final boolean mUse4ByteSequence;
-
- private OutputStream mOut;
- private int mBufferPos;
-
- /**
- * Values that have been "interned" by {@link #writeInternedUTF(String)}.
- */
- private final HashMap<String, Integer> mStringRefs = new HashMap<>();
-
- /**
- * @deprecated callers must specify {@code use4ByteSequence} so they make a
- * clear choice about working around a long-standing ART bug, as
- * described by the {@code kUtfUse4ByteSequence} comments in
- * {@code art/runtime/jni/jni_internal.cc}.
- */
- @Deprecated
- public FastDataOutput(@NonNull OutputStream out, int bufferSize) {
- this(out, bufferSize, true /* use4ByteSequence */);
- }
-
- public FastDataOutput(@NonNull OutputStream out, int bufferSize, boolean use4ByteSequence) {
- mRuntime = VMRuntime.getRuntime();
- if (bufferSize < 8) {
- throw new IllegalArgumentException();
- }
-
- mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
- mBufferPtr = mRuntime.addressOf(mBuffer);
- mBufferCap = mBuffer.length;
- mUse4ByteSequence = use4ByteSequence;
-
- setOutput(out);
- }
-
- /**
- * Obtain a {@link FastDataOutput} configured with the given
- * {@link OutputStream} and which encodes large code-points using 3-byte
- * sequences.
- * <p>
- * This <em>is</em> compatible with the {@link DataOutput} API contract,
- * which specifies that large code-points must be encoded with 3-byte
- * sequences.
- */
- public static FastDataOutput obtainUsing3ByteSequences(@NonNull OutputStream out) {
- return new FastDataOutput(out, DEFAULT_BUFFER_SIZE, false /* use4ByteSequence */);
- }
-
- /**
- * Obtain a {@link FastDataOutput} configured with the given
- * {@link OutputStream} and which encodes large code-points using 4-byte
- * sequences.
- * <p>
- * This <em>is not</em> compatible with the {@link DataOutput} API contract,
- * which specifies that large code-points must be encoded with 3-byte
- * sequences.
- */
- public static FastDataOutput obtainUsing4ByteSequences(@NonNull OutputStream out) {
- FastDataOutput instance = sOutCache.getAndSet(null);
- if (instance != null) {
- instance.setOutput(out);
- return instance;
- }
- return new FastDataOutput(out, DEFAULT_BUFFER_SIZE, true /* use4ByteSequence */);
- }
-
- /**
- * Release a {@link FastDataOutput} to potentially be recycled. You must not
- * interact with the object after releasing it.
- */
- public void release() {
- if (mBufferPos > 0) {
- throw new IllegalStateException("Lingering data, call flush() before releasing.");
- }
-
- mOut = null;
- mBufferPos = 0;
- mStringRefs.clear();
-
- if (mBufferCap == DEFAULT_BUFFER_SIZE && mUse4ByteSequence) {
- // Try to return to the cache.
- sOutCache.compareAndSet(null, this);
- }
- }
-
- /**
- * Re-initializes the object for the new output.
- */
- private void setOutput(@NonNull OutputStream out) {
- mOut = Objects.requireNonNull(out);
- mBufferPos = 0;
- mStringRefs.clear();
- }
-
- private void drain() throws IOException {
- if (mBufferPos > 0) {
- mOut.write(mBuffer, 0, mBufferPos);
- mBufferPos = 0;
- }
- }
-
- @Override
- public void flush() throws IOException {
- drain();
- mOut.flush();
- }
-
- @Override
- public void close() throws IOException {
- mOut.close();
- release();
- }
-
- @Override
- public void write(int b) throws IOException {
- writeByte(b);
- }
-
- @Override
- public void write(byte[] b) throws IOException {
- write(b, 0, b.length);
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- if (mBufferCap < len) {
- drain();
- mOut.write(b, off, len);
- } else {
- if (mBufferCap - mBufferPos < len) drain();
- System.arraycopy(b, off, mBuffer, mBufferPos, len);
- mBufferPos += len;
- }
- }
-
- @Override
- public void writeUTF(String s) throws IOException {
- if (mUse4ByteSequence) {
- writeUTFUsing4ByteSequences(s);
- } else {
- writeUTFUsing3ByteSequences(s);
- }
- }
-
- private void writeUTFUsing4ByteSequences(String s) throws IOException {
- // Attempt to write directly to buffer space if there's enough room,
- // otherwise fall back to chunking into place
- if (mBufferCap - mBufferPos < 2 + s.length()) drain();
-
- // Magnitude of this returned value indicates the number of bytes
- // required to encode the string; sign indicates success/failure
- int len = CharsetUtils.toModifiedUtf8Bytes(s, mBufferPtr, mBufferPos + 2, mBufferCap);
- if (Math.abs(len) > MAX_UNSIGNED_SHORT) {
- throw new IOException("Modified UTF-8 length too large: " + len);
- }
-
- if (len >= 0) {
- // Positive value indicates the string was encoded into the buffer
- // successfully, so we only need to prefix with length
- writeShort(len);
- mBufferPos += len;
- } else {
- // Negative value indicates buffer was too small and we need to
- // allocate a temporary buffer for encoding
- len = -len;
- final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
- CharsetUtils.toModifiedUtf8Bytes(s, mRuntime.addressOf(tmp), 0, tmp.length);
- writeShort(len);
- write(tmp, 0, len);
- }
- }
-
- private void writeUTFUsing3ByteSequences(String s) throws IOException {
- final int len = (int) ModifiedUtf8.countBytes(s, false);
- if (len > MAX_UNSIGNED_SHORT) {
- throw new IOException("Modified UTF-8 length too large: " + len);
- }
-
- // Attempt to write directly to buffer space if there's enough room,
- // otherwise fall back to chunking into place
- if (mBufferCap >= 2 + len) {
- if (mBufferCap - mBufferPos < 2 + len) drain();
- writeShort(len);
- ModifiedUtf8.encode(mBuffer, mBufferPos, s);
- mBufferPos += len;
- } else {
- final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
- ModifiedUtf8.encode(tmp, 0, s);
- writeShort(len);
- write(tmp, 0, len);
- }
- }
-
- /**
- * Write a {@link String} value with the additional signal that the given
- * value is a candidate for being canonicalized, similar to
- * {@link String#intern()}.
- * <p>
- * Canonicalization is implemented by writing each unique string value once
- * the first time it appears, and then writing a lightweight {@code short}
- * reference when that string is written again in the future.
- *
- * @see FastDataInput#readInternedUTF()
- */
- public void writeInternedUTF(@NonNull String s) throws IOException {
- Integer ref = mStringRefs.get(s);
- if (ref != null) {
- writeShort(ref);
- } else {
- writeShort(MAX_UNSIGNED_SHORT);
- writeUTF(s);
-
- // We can only safely intern when we have remaining values; if we're
- // full we at least sent the string value above
- ref = mStringRefs.size();
- if (ref < MAX_UNSIGNED_SHORT) {
- mStringRefs.put(s, ref);
- }
- }
- }
-
- @Override
- public void writeBoolean(boolean v) throws IOException {
- writeByte(v ? 1 : 0);
- }
-
- @Override
- public void writeByte(int v) throws IOException {
- if (mBufferCap - mBufferPos < 1) drain();
- mBuffer[mBufferPos++] = (byte) ((v >> 0) & 0xff);
- }
-
- @Override
- public void writeShort(int v) throws IOException {
- if (mBufferCap - mBufferPos < 2) drain();
- mBuffer[mBufferPos++] = (byte) ((v >> 8) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((v >> 0) & 0xff);
- }
-
- @Override
- public void writeChar(int v) throws IOException {
- writeShort((short) v);
- }
-
- @Override
- public void writeInt(int v) throws IOException {
- if (mBufferCap - mBufferPos < 4) drain();
- mBuffer[mBufferPos++] = (byte) ((v >> 24) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((v >> 16) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((v >> 8) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((v >> 0) & 0xff);
- }
-
- @Override
- public void writeLong(long v) throws IOException {
- if (mBufferCap - mBufferPos < 8) drain();
- int i = (int) (v >> 32);
- mBuffer[mBufferPos++] = (byte) ((i >> 24) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((i >> 16) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((i >> 8) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((i >> 0) & 0xff);
- i = (int) v;
- mBuffer[mBufferPos++] = (byte) ((i >> 24) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((i >> 16) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((i >> 8) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((i >> 0) & 0xff);
- }
-
- @Override
- public void writeFloat(float v) throws IOException {
- writeInt(Float.floatToIntBits(v));
- }
-
- @Override
- public void writeDouble(double v) throws IOException {
- writeLong(Double.doubleToLongBits(v));
- }
-
- @Override
- public void writeBytes(String s) throws IOException {
- // Callers should use writeUTF()
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void writeChars(String s) throws IOException {
- // Callers should use writeUTF()
- throw new UnsupportedOperationException();
- }
-}
diff --git a/core/java/com/android/internal/util/ModifiedUtf8.java b/core/java/com/android/internal/util/ModifiedUtf8.java
deleted file mode 100644
index a144c00..0000000
--- a/core/java/com/android/internal/util/ModifiedUtf8.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.util;
-
-import java.io.UTFDataFormatException;
-
-public class ModifiedUtf8 {
- /**
- * Decodes a byte array containing <i>modified UTF-8</i> bytes into a string.
- *
- * <p>Note that although this method decodes the (supposedly impossible) zero byte to U+0000,
- * that's what the RI does too.
- */
- public static String decode(byte[] in, char[] out, int offset, int utfSize)
- throws UTFDataFormatException {
- int count = 0, s = 0, a;
- while (count < utfSize) {
- if ((out[s] = (char) in[offset + count++]) < '\u0080') {
- s++;
- } else if (((a = out[s]) & 0xe0) == 0xc0) {
- if (count >= utfSize) {
- throw new UTFDataFormatException("bad second byte at " + count);
- }
- int b = in[offset + count++];
- if ((b & 0xC0) != 0x80) {
- throw new UTFDataFormatException("bad second byte at " + (count - 1));
- }
- out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
- } else if ((a & 0xf0) == 0xe0) {
- if (count + 1 >= utfSize) {
- throw new UTFDataFormatException("bad third byte at " + (count + 1));
- }
- int b = in[offset + count++];
- int c = in[offset + count++];
- if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
- throw new UTFDataFormatException("bad second or third byte at " + (count - 2));
- }
- out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
- } else {
- throw new UTFDataFormatException("bad byte at " + (count - 1));
- }
- }
- return new String(out, 0, s);
- }
-
- /**
- * Returns the number of bytes the modified UTF-8 representation of 's' would take. Note
- * that this is just the space for the bytes representing the characters, not the length
- * which precedes those bytes, because different callers represent the length differently,
- * as two, four, or even eight bytes. If {@code shortLength} is true, we'll throw an
- * exception if the string is too long for its length to be represented by a short.
- */
- public static long countBytes(String s, boolean shortLength) throws UTFDataFormatException {
- long result = 0;
- final int length = s.length();
- for (int i = 0; i < length; ++i) {
- char ch = s.charAt(i);
- if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
- ++result;
- } else if (ch <= 2047) {
- result += 2;
- } else {
- result += 3;
- }
- if (shortLength && result > 65535) {
- throw new UTFDataFormatException("String more than 65535 UTF bytes long");
- }
- }
- return result;
- }
-
- /**
- * Encodes the <i>modified UTF-8</i> bytes corresponding to string {@code s} into the
- * byte array {@code dst}, starting at the given {@code offset}.
- */
- public static void encode(byte[] dst, int offset, String s) {
- final int length = s.length();
- for (int i = 0; i < length; i++) {
- char ch = s.charAt(i);
- if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
- dst[offset++] = (byte) ch;
- } else if (ch <= 2047) {
- dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6)));
- dst[offset++] = (byte) (0x80 | (0x3f & ch));
- } else {
- dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12)));
- dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6)));
- dst[offset++] = (byte) (0x80 | (0x3f & ch));
- }
- }
- }
-
- private ModifiedUtf8() {
- }
-}
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index de6b65f3..af5e3b3 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -25,10 +25,11 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Base64;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import libcore.util.HexEncoding;
import org.xmlpull.v1.XmlPullParser;
diff --git a/core/jni/android_view_VelocityTracker.cpp b/core/jni/android_view_VelocityTracker.cpp
index 16b9f00..343e9d8 100644
--- a/core/jni/android_view_VelocityTracker.cpp
+++ b/core/jni/android_view_VelocityTracker.cpp
@@ -153,6 +153,11 @@
return result;
}
+static jboolean android_view_VelocityTracker_nativeIsAxisSupported(JNIEnv* env, jclass clazz,
+ jint axis) {
+ return VelocityTracker::isAxisSupported(axis);
+}
+
// --- JNI Registration ---
static const JNINativeMethod gVelocityTrackerMethods[] = {
@@ -167,6 +172,8 @@
{"nativeGetVelocity", "(JII)F", (void*)android_view_VelocityTracker_nativeGetVelocity},
{"nativeGetEstimator", "(JIILandroid/view/VelocityTracker$Estimator;)Z",
(void*)android_view_VelocityTracker_nativeGetEstimator},
+ {"nativeIsAxisSupported", "(I)Z",
+ (void*)android_view_VelocityTracker_nativeIsAxisSupported},
};
int register_android_view_VelocityTracker(JNIEnv* env) {
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 5099dd2..9e4f63c 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -977,6 +977,7 @@
optional int32 profile = 2;
}
repeated UserProfile user_profile_group_ids = 4;
+ repeated int32 visible_users_array = 5;
}
// sync with com.android.server.am.AppTimeTracker.java
diff --git a/core/res/res/color/letterbox_background.xml b/core/res/res/color/letterbox_background.xml
new file mode 100644
index 0000000..955948a
--- /dev/null
+++ b/core/res/res/color/letterbox_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/system_neutral1_500" android:lStar="5" />
+</selector>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 4b9abad..0ebce40 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Snelsluit"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nuwe kennisgewing"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuele sleutelbord"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fisieke sleutelbord"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sekuriteit"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Motormodus"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index b6fdcdf..a861f3c 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"መቆለፊያ"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"አዲስ ማሳወቂያ"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ምናባዊ የቁልፍ ሰሌዳ"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"አካላዊ ቁልፍ ሰሌዳ"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ደህንነት"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"የመኪና ሁነታ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 977cf8e..7623fb1 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -268,7 +268,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"إلغاء التأمين"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"إشعار جديد"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"لوحة المفاتيح الافتراضية"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"لوحة المفاتيح الخارجية"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"الأمان"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"وضع السيارة"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index dbce595..3a97baf 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"লকডাউন"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"৯৯৯+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"নতুন জাননী"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ভাৰ্শ্বুৱল কীব\'ৰ্ড"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"কায়িক কীব’ৰ্ড"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"সুৰক্ষা"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"গাড়ী ম\'ড"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 586adef..0b361ac 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Kilidləyin"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Yeni bildiriş"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual klaviatura"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fiziki klaviatura"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Güvənlik"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Avtomobil rejimi"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index c19338d..078c098 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Zaključavanje"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Novo obaveštenje"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuelna tastatura"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizička tastatura"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Bezbednost"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Režim rada u automobilu"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index ab338c7..023e82c 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Блакіроўка"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Новае апавяшчэнне"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Віртуальная клавіятура"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Фізічная клавіятура"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Бяспека"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Рэжым \"У машыне\""</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 46ab1ad..aaa080a 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Заключване"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Ново известие"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуална клавиатура"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физическа клавиатура"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Сигурност"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Моторежим"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index ed77eef..ee1db8e 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"লকডাউন"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"৯৯৯+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"নতুন বিজ্ঞপ্তি"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ভার্চুয়াল কীবোর্ড"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ফিজিক্যাল কীবোর্ড"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"নিরাপত্তা"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"গাড়ি মোড"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 081d8f2..20f6bc1 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Zaključavanje"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Novo obavještenje"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuelna tastatura"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizička tastatura"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sigurnost"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Način rada u automobilu"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index aad5668..2142b60 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueig de seguretat"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"+999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Notificació nova"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclat virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclat físic"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Seguretat"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Mode de cotxe"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index e9d5ab2..7720d08 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Zamknuto"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nové oznámení"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuální klávesnice"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fyzická klávesnice"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Zabezpečení"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Režim Auto"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index edb962c..ecd6407 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lås enhed"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Ny notifikation"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuelt tastatur"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysisk tastatur"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sikkerhed"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Biltilstand"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index b7a0b02..3d5985c9 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Sperren"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Neue Benachrichtigung"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Bildschirmtastatur"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physische Tastatur"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sicherheit"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Automodus"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index a9dd1cf..f84a9fb 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Κλείδωμα"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Νέα ειδοποίηση"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Εικονικό πληκτρολόγιο"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Κανονικό πληκτρολόγιο"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Ασφάλεια"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Λειτουργία αυτοκινήτου"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 1cc8d50..4c0510b 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual keyboard"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Security"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Car mode"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 6fa02f3..875ddf9 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual keyboard"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Security"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Car mode"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index fac706e..6e034b7 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual keyboard"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Security"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Car mode"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 55c121ac..643f27f 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual keyboard"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Security"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Car mode"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 1b190e3..91e99ff 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual keyboard"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Security"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Car mode"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 106935c2..6a45205 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueo"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Notificación nueva"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclado virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Seguridad"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modo auto"</string>
@@ -1970,8 +1969,8 @@
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Presiona para ver archivos"</string>
<string name="pin_target" msgid="8036028973110156895">"Fijar"</string>
<string name="pin_specific_target" msgid="7824671240625957415">"Fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
- <string name="unpin_target" msgid="3963318576590204447">"No fijar"</string>
- <string name="unpin_specific_target" msgid="3859828252160908146">"No fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="unpin_target" msgid="3963318576590204447">"Dejar de fijar"</string>
+ <string name="unpin_specific_target" msgid="3859828252160908146">"Dejar de fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="app_info" msgid="6113278084877079851">"Información de apps"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Iniciando demostración…"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 3ae013b..66f67b3 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueo de seguridad"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"> 999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Notificación nueva"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclado virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Seguridad"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modo de coche"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 182aa65..349a6b2 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lukustamine"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Uus märguanne"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuaalne klaviatuur"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Füüsiline klaviatuur"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Turvalisus"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Autorežiim"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 31cc0b6..d4759d5 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Blokeoa"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Jakinarazpen berria"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teklatu birtuala"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teklatu fisikoa"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Segurtasuna"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Auto modua"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 58a7f62..4064353 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"قفل همه"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"۹۹۹+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"اعلان جدید"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"صفحهکلید مجازی"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"صفحهکلید فیزیکی"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"امنیت"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"حالت خودرو"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 31d2571..8fedfb7 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lukitse"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Uusi ilmoitus"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuaalinen näppäimistö"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fyysinen näppäimistö"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Turvallisuus"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Autotila"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 5150da9..e63b734 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Verrouillage"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nouvelle notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Clavier virtuel"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Clavier physique"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sécurité"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Mode Voiture"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 3736890..019fdf2 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Verrouiller"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nouvelle notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Clavier virtuel"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Clavier physique"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sécurité"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Mode Voiture"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 7575d68..219299f 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloquear"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Notificación nova"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclado virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Seguranza"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modo coche"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 42bad0a..90dda1a 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"લૉકડાઉન"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"નવું નોટિફિકેશન"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"વર્ચ્યુઅલ કીબોર્ડ"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ભૌતિક કીબોર્ડ"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"સુરક્ષા"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"કાર મોડ"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 83cacea..af5bc1f 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"फ़ाेन लॉक करें"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"नई सूचना"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"वर्चुअल कीबोर्ड"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"सामान्य कीबोर्ड"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"सुरक्षा"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"कार मोड"</string>
@@ -1136,7 +1135,7 @@
<string name="copy" msgid="5472512047143665218">"कॉपी करें"</string>
<string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"क्लिपबोर्ड पर कॉपी नहीं हो सका"</string>
<string name="paste" msgid="461843306215520225">"चिपकाएं"</string>
- <string name="paste_as_plain_text" msgid="7664800665823182587">"सादे पाठ के रूप में चिपकाएं"</string>
+ <string name="paste_as_plain_text" msgid="7664800665823182587">"सादे टेक्स्ट के रूप में चिपकाएं"</string>
<string name="replace" msgid="7842675434546657444">"बदलें•"</string>
<string name="delete" msgid="1514113991712129054">"मिटाएं"</string>
<string name="copyUrl" msgid="6229645005987260230">"यूआरएल को कॉपी करें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 6f81009..87df29a 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Zaključavanje"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nova obavijest"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtualna tipkovnica"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizička tipkovnica"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sigurnost"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Način rada u automobilu"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 71687d4..3762fde 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Zárolás"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Új értesítés"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuális billentyűzet"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizikai billentyűzet"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Biztonság"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Autós üzemmód"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index b7146f0..a11e24b 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Արգելափակում"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Նոր ծանուցում"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Վիրտուալ ստեղնաշար"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Ֆիզիկական ստեղնաշար"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Անվտանգություն"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Մեքենայի ռեժիմ"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 66fc3fb..dbccee9 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Kunci total"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Notifikasi baru"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Keyboard virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Keyboard fisik"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Keamanan"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Mode mobil"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 64b2340..cfefc03 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Læsing"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Ný tilkynning"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Sýndarlyklaborð"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Vélbúnaðarlyklaborð"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Öryggi"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Bílastilling"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index d25bb06..b05bf79 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Blocco"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nuova notifica"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Tastiera virtuale"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tastiera fisica"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sicurezza"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modalità automobile"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index a1373c3..8656fce 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"נעילה"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"התראה חדשה"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"מקלדת וירטואלית"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"מקלדת פיזית"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"אבטחה"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"מצב נהיגה"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index ed55a7f..69d0b9d 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ロックダウン"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"新しい通知"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"仮想キーボード"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"物理キーボード"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"セキュリティ"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"運転モード"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 0b06d7c..6d32f25 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"დაბლოკვა"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"ახალი შეტყობინება"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ვირტუალური კლავიატურა"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ფიზიკური კლავიატურა"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"უსაფრთხოება"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"მანქანის რეჟიმი"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index a44d09d..0d9fd3f 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Құлыптау"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Жаңа хабарландыру"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуалдық пернетақта"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физикалық пернетақта"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Қауіпсіздік"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Көлік режимі"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 2fada73..0c82b66 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ការចាក់សោ"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"ការជូនដំណឹងថ្មី"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ក្ដារចុចនិម្មិត"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ក្ដារចុចរូបវន្ត"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"សុវត្ថិភាព"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"មុខងាររថយន្ត"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index c39d9f7..e27527f 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ಲಾಕ್ಡೌನ್"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"ಹೊಸ ಅಧಿಸೂಚನೆ"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ವರ್ಚುಯಲ್ ಕೀಬೋರ್ಡ್"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ಭೌತಿಕ ಕೀಬೋರ್ಡ್"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ಭದ್ರತೆ"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"ಕಾರ್ ಮೋಡ್"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 55bae4d..c953a39 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -247,7 +247,7 @@
<string name="bugreport_message" msgid="5212529146119624326">"현재 기기 상태에 대한 정보를 수집하여 이메일 메시지로 전송합니다. 버그 신고를 시작하여 전송할 준비가 되려면 약간 시간이 걸립니다."</string>
<string name="bugreport_option_interactive_title" msgid="7968287837902871289">"대화형 보고서"</string>
<string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"대부분의 경우 이 옵션을 사용합니다. 신고 진행 상황을 추적하고 문제에 대한 세부정보를 입력하고 스크린샷을 찍을 수 있습니다. 신고하기에 시간이 너무 오래 걸리고 사용 빈도가 낮은 일부 섹션을 생략할 수 있습니다."</string>
- <string name="bugreport_option_full_title" msgid="7681035745950045690">"전체 보고서"</string>
+ <string name="bugreport_option_full_title" msgid="7681035745950045690">"전체 신고"</string>
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"기기가 응답하지 않거나 너무 느리거나 모든 보고서 섹션이 필요한 경우 이 옵션을 사용하여 시스템 방해를 최소화합니다. 세부정보를 추가하거나 스크린샷을 추가로 찍을 수 없습니다."</string>
<string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{버그 신고 스크린샷을 #초 후에 찍습니다.}other{버그 신고 스크린샷을 #초 후에 찍습니다.}}"</string>
<string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"버그 신고용 스크린샷 촬영 완료"</string>
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"잠금"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"새 알림"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"가상 키보드"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"물리적 키보드"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"보안"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"운전 모드"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index ad01dafc..dccc4a6 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Бекем кулпулоо"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Жаңы эскертме"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуалдык баскычтоп"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Аппараттык баскычтоп"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Коопсуздук"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Унаа режими"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 02df227..c6524de 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ລັອກໄວ້"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"ການແຈ້ງເຕືອນໃໝ່"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ແປ້ນພິມສະເໝືອນ"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ແປ້ນພິມພາຍນອກ"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ຄວາມປອດໄພ"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"ໂໝດຂັບລົດ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 4543ef6..adf30e8 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Užrakinimas"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Naujas pranešimas"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtualioji klaviatūra"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizinė klaviatūra"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sauga"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Automobilio režimas"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 64c855b..5631521 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloķēšana"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"Pārsniedz"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Jauns paziņojums"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuālā tastatūra"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fiziskā tastatūra"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Drošība"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Automašīnas režīms"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 0af4cdd..a45d0a7 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Заклучување"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Ново известување"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуелна тастатура"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физичка тастатура"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Безбедност"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Режим на работа во автомобил"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index bc12c07..492cd54 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ലോക്ക്ഡൗൺ"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"പുതിയ അറിയിപ്പ്"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"വെർച്വൽ കീബോഡ്"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ഫിസിക്കൽ കീബോഡ്"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"സുരക്ഷ"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"കാർ മോഡ്"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 4e8c314..2c8aaae 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Түгжих"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Шинэ мэдэгдэл"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуал гар"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Биет гар"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Аюулгүй байдал"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Машины горим"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index aa8c1e9..d47fea35 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"लॉकडाउन"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"नवीन सूचना"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"व्हर्च्युअल कीबोर्ड"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"वास्तविक कीबोर्ड"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"सुरक्षा"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"कार मोड"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 9a6ee3b..deb343d 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Kunci semua"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Pemberitahuan baharu"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Papan kekunci maya"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Papan kekunci fizikal"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Keselamatan"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Mod kereta"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 716f5d7..5f41672 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"လော့ခ်ဒေါင်း"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"၉၉၉+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"အကြောင်းကြားချက်အသစ်"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ပကတိအသွင်ကီးဘုတ်"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"စက်၏ ကီးဘုတ်"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"လုံခြုံရေး"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"ကားမုဒ်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 3d16ea7..0c9a983 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Låsing"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nytt varsel"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuelt tastatur"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysisk tastatur"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sikkerhet"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Bilmodus"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index bdb31bc..e95d48b 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"लकडाउन गर्नु…"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"९९९+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"नयाँ सूचना"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"भर्चुअल किबोर्ड"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"फिजिकल किबोर्ड"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"सुरक्षा"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"कार मोड"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 162d3e8..5723510 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999 +"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nieuwe melding"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtueel toetsenbord"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysiek toetsenbord"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Beveiliging"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Automodus"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 440245e..c4150dc 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ଲକ୍ କରନ୍ତୁ"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"ନୂଆ ବିଜ୍ଞପ୍ତି"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ଭର୍ଚୁଆଲ୍ କୀ\'ବୋର୍ଡ"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ଫିଜିକଲ୍ କୀ’ବୋର୍ଡ"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ସୁରକ୍ଷା"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"କାର୍ ମୋଡ୍"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index a9e3531..96917c0 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ਲਾਕਡਾਊਨ"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"ਨਵੀਂ ਸੂਚਨਾ"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ਆਭਾਸੀ ਕੀ-ਬੋਰਡ"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ਭੌਤਿਕ ਕੀ-ਬੋਰਡ"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ਸੁਰੱਖਿਆ"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"ਕਾਰ ਮੋਡ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 7b47a5c..be9d322 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Blokada"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nowe powiadomienie"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Klawiatura wirtualna"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Klawiatura fizyczna"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Bezpieczeństwo"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Tryb samochodowy"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 8635d76..ff352b1 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueio total"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nova notificação"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclado virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Segurança"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modo carro"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 5dc59e1..d343af6 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloquear"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nova notificação"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclado virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Segurança"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modo automóvel"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 8635d76..ff352b1 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueio total"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nova notificação"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclado virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Segurança"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modo carro"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index acd1df6..b560b07 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Blocare strictă"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"˃999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Notificare nouă"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Tastatură virtuală"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tastatură fizică"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Securitate"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Mod Mașină"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index fb4775b..fbe67e2 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Блокировка входа"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Новое уведомление"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуальная клавиатура"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физическая клавиатура"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Безопасность"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Режим \"В авто\""</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 098e622..4cec877 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"අගුලු දැමීම"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"නව දැනුම්දීම"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"අතථ්ය යතුරු පුවරුව"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"භෞතික යතුරු පුවරුව"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ආරක්ෂාව"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"මෝටර් රථ ආකාරය"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 47e3d3b..b98364a 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Uzamknúť"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nové upozornenie"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuálna klávesnica"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fyzická klávesnica"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Zabezpečenie"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Režim v aute"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 6e722e3..6972abb 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Zakleni"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999 +"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Novo obvestilo"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Navidezna tipkovnica"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizična tipkovnica"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Varnost"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Način za avtomobil"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 0a70e9a..cbac0f0 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Blloko"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Njoftim i ri"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Tastiera virtuale"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tastiera fizike"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Siguria"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modaliteti \"në makinë\""</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 0e50810..d5549e7 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Закључавање"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Ново обавештење"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуелна тастатура"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физичка тастатура"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Безбедност"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Режим рада у аутомобилу"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index f8b3fdf..d1c579d 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Låsning"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Ny avisering"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuellt tangentbord"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysiskt tangentbord"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Säkerhet"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Billäge"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 5829fae..2bc3f57 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Funga"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Arifa mpya"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Kibodi pepe"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Kibodi halisi"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Usalama"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Hali ya gari"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 1533c36..9e48a47 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"பூட்டு"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"புதிய அறிவிப்பு"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"விர்ச்சுவல் கீபோர்டு"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"கைமுறை கீபோர்டு"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"பாதுகாப்பு"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"கார் பயன்முறை"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 313bc80..eff08bc 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"లాక్ చేయి"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"కొత్త నోటిఫికేషన్"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"వర్చువల్ కీబోర్డ్"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"భౌతిక కీబోర్డ్"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"సెక్యూరిటీ"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"కార్ మోడ్"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 447b42b..72cbc36 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ปิดล็อก"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"การแจ้งเตือนใหม่"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"แป้นพิมพ์เสมือน"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"แป้นพิมพ์จริง"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ความปลอดภัย"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"โหมดรถยนต์"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 9aaa1b8..e66999d 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"I-lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Bagong notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual na keyboard"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Pisikal na keyboard"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Seguridad"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Car mode"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 2f80037..b6c4b4a 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Tam gizlilik"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Yeni bildirim"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Sanal klavye"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fiziksel klavye"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Güvenlik"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Araç modu"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index b7bb63b..c408c51 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Блокування"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Нове сповіщення"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Віртуальна клавіатура"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Фізична клавіатура"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Безпека"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Режим автомобіля"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 58e342f..7784431 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"مقفل"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"نئی اطلاع"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ورچوئل کی بورڈ"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"فزیکل کی بورڈ"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"سیکیورٹی"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"کار وضع"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index cf4478d..5ac689d1 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloklash"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Yangi bildirishnoma"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual klaviatura"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tashqi klaviatura"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Xavfsizlik"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Avtomobil rejimi"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 1a1bb91..e1b479c 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Khóa"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Thông báo mới"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Bàn phím ảo"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Bàn phím vật lý"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Bảo mật"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Chế độ trên ô tô"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 4cb587b..5fce25e 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"锁定"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"新通知"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"虚拟键盘"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"实体键盘"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"安全性"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"车载模式"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index b9185b7..61a3f20 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"鎖定"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"新通知"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"虛擬鍵盤"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"實體鍵盤"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"安全性"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"車用模式"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 4baced8..10dc699 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"鎖定"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"超過 999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"新通知"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"虛擬鍵盤"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"實體鍵盤"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"安全性"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"車用模式"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 23a557a..66d639e 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Khiya"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Isaziso esisha"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Ikhibhodi ebonakalayo"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Ikhibhodi ephathekayo"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Ukuphepha"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Imodi yemoto"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 173908d..23dd1b4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5217,7 +5217,7 @@
but isn't supported on the device or both dark scrim alpha and blur radius aren't
provided.
-->
- <color name="config_letterboxBackgroundColor">@android:color/system_neutral2_900</color>
+ <color name="config_letterboxBackgroundColor">@color/letterbox_background</color>
<!-- Horizontal position of a center of the letterboxed app window.
0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
new file mode 100644
index 0000000..fe3ab62
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2022 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.radio.tests.unittests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.hardware.radio.IRadioService;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.RadioTuner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class TunerAdapterTest {
+
+ private static final int CALLBACK_TIMEOUT_MS = 30_000;
+ private static final int AM_LOWER_LIMIT_KHZ = 150;
+
+ private static final RadioManager.BandConfig TEST_BAND_CONFIG = createBandConfig();
+
+ private static final ProgramSelector.Identifier FM_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+ /* value= */ 94300);
+ private static final ProgramSelector FM_SELECTOR =
+ new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER,
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+ private static final RadioManager.ProgramInfo FM_PROGRAM_INFO = createFmProgramInfo();
+
+ private RadioTuner mRadioTuner;
+ private ITunerCallback mTunerCallback;
+
+ @Mock
+ private IRadioService mRadioServiceMock;
+ @Mock
+ private Context mContextMock;
+ @Mock
+ private ITuner mTunerMock;
+ @Mock
+ private RadioTuner.Callback mCallbackMock;
+
+ @Before
+ public void setUp() throws Exception {
+ RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
+
+ doAnswer(invocation -> {
+ mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
+ return mTunerMock;
+ }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
+
+ doAnswer(invocation -> {
+ ProgramSelector program = (ProgramSelector) invocation.getArguments()[0];
+ if (program.getPrimaryId().getType()
+ != ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY) {
+ throw new IllegalArgumentException();
+ }
+ if (program.getPrimaryId().getValue() < AM_LOWER_LIMIT_KHZ) {
+ mTunerCallback.onTuneFailed(RadioManager.STATUS_BAD_VALUE, program);
+ } else {
+ mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).tune(any());
+
+ mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, TEST_BAND_CONFIG,
+ /* withAudio= */ true, mCallbackMock, /* handler= */ null);
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ mRadioTuner.close();
+ }
+
+ @Test
+ public void close_forTunerAdapter() throws Exception {
+ mRadioTuner.close();
+
+ verify(mTunerMock).close();
+ }
+
+ @Test
+ public void setConfiguration_forTunerAdapter() throws Exception {
+ int status = mRadioTuner.setConfiguration(TEST_BAND_CONFIG);
+
+ verify(mTunerMock).setConfiguration(TEST_BAND_CONFIG);
+ assertWithMessage("Status for setting configuration")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ }
+
+ @Test
+ public void getConfiguration_forTunerAdapter() throws Exception {
+ when(mTunerMock.getConfiguration()).thenReturn(TEST_BAND_CONFIG);
+ RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[1];
+
+ int status = mRadioTuner.getConfiguration(bandConfigs);
+
+ assertWithMessage("Status for getting configuration")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ assertWithMessage("Configuration obtained from radio tuner")
+ .that(bandConfigs[0]).isEqualTo(TEST_BAND_CONFIG);
+ }
+
+ @Test
+ public void setMute_forTunerAdapter() {
+ int status = mRadioTuner.setMute(/* mute= */ true);
+
+ assertWithMessage("Status for setting mute")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ }
+
+ @Test
+ public void getMute_forTunerAdapter() throws Exception {
+ when(mTunerMock.isMuted()).thenReturn(true);
+
+ boolean muteStatus = mRadioTuner.getMute();
+
+ assertWithMessage("Mute status").that(muteStatus).isTrue();
+ }
+
+ @Test
+ public void step_forTunerAdapter_succeeds() throws Exception {
+ doAnswer(invocation -> {
+ mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).step(anyBoolean(), anyBoolean());
+
+ int scanStatus = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
+
+ verify(mTunerMock).step(/* skipSubChannel= */ true, /* skipSubChannel= */ false);
+ assertWithMessage("Status for stepping")
+ .that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+ @Test
+ public void seek_forTunerAdapter_succeeds() throws Exception {
+ doAnswer(invocation -> {
+ mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).scan(anyBoolean(), anyBoolean());
+
+ int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
+
+ verify(mTunerMock).scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+ assertWithMessage("Status for seeking")
+ .that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+ @Test
+ public void seek_forTunerAdapter_invokesOnErrorWhenTimeout() throws Exception {
+ doAnswer(invocation -> {
+ mTunerCallback.onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).scan(anyBoolean(), anyBoolean());
+
+ mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel*/ true);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+ }
+
+ @Test
+ public void tune_withChannelsForTunerAdapter_succeeds() {
+ int status = mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0);
+
+ assertWithMessage("Status for tuning with channel and sub-channel")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+ @Test
+ public void tune_withValidSelectorForTunerAdapter_succeeds() throws Exception {
+ mRadioTuner.tune(FM_SELECTOR);
+
+ verify(mTunerMock).tune(FM_SELECTOR);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+
+ @Test
+ public void tune_withInvalidSelectorForTunerAdapter_invokesOnTuneFailed() {
+ ProgramSelector invalidSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
+ new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 100),
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+
+ mRadioTuner.tune(invalidSelector);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onTuneFailed(RadioManager.STATUS_BAD_VALUE, invalidSelector);
+ }
+
+ @Test
+ public void cancel_forTunerAdapter() throws Exception {
+ mRadioTuner.tune(FM_SELECTOR);
+
+ mRadioTuner.cancel();
+
+ verify(mTunerMock).cancel();
+ }
+
+ @Test
+ public void cancelAnnouncement_forTunerAdapter() throws Exception {
+ mRadioTuner.cancelAnnouncement();
+
+ verify(mTunerMock).cancelAnnouncement();
+ }
+
+ @Test
+ public void getProgramInfo_beforeProgramInfoSetForTunerAdapter() {
+ RadioManager.ProgramInfo[] programInfoArray = new RadioManager.ProgramInfo[1];
+
+ int status = mRadioTuner.getProgramInformation(programInfoArray);
+
+ assertWithMessage("Status for getting null program info")
+ .that(status).isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+ }
+
+ @Test
+ public void getProgramInfo_afterTuneForTunerAdapter() {
+ mRadioTuner.tune(FM_SELECTOR);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ RadioManager.ProgramInfo[] programInfoArray = new RadioManager.ProgramInfo[1];
+
+ int status = mRadioTuner.getProgramInformation(programInfoArray);
+
+ assertWithMessage("Status for getting program info")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ assertWithMessage("Program info obtained from radio tuner")
+ .that(programInfoArray[0]).isEqualTo(FM_PROGRAM_INFO);
+ }
+
+ @Test
+ public void getMetadataImage_forTunerAdapter() throws Exception {
+ Bitmap bitmapExpected = Mockito.mock(Bitmap.class);
+ when(mTunerMock.getImage(anyInt())).thenReturn(bitmapExpected);
+ int imageId = 1;
+
+ Bitmap image = mRadioTuner.getMetadataImage(/* id= */ imageId);
+
+ assertWithMessage("Image obtained from id %s", imageId)
+ .that(image).isEqualTo(bitmapExpected);
+ }
+
+ @Test
+ public void isAnalogForced_forTunerAdapter() throws Exception {
+ when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);
+
+ boolean isAnalogForced = mRadioTuner.isAnalogForced();
+
+ assertWithMessage("Forced analog playback switch")
+ .that(isAnalogForced).isTrue();
+ }
+
+ @Test
+ public void setAnalogForced_forTunerAdapter() throws Exception {
+ boolean analogForced = true;
+
+ mRadioTuner.setAnalogForced(analogForced);
+
+ verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, analogForced);
+ }
+
+ @Test
+ public void isConfigFlagSupported_forTunerAdapter() throws Exception {
+ when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING))
+ .thenReturn(true);
+
+ boolean dabFmSoftLinking =
+ mRadioTuner.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING);
+
+ assertWithMessage("Support for DAB-DAB linking config flag")
+ .that(dabFmSoftLinking).isTrue();
+ }
+
+ @Test
+ public void isConfigFlagSet_forTunerAdapter() throws Exception {
+ when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_DAB_FM_SOFT_LINKING))
+ .thenReturn(true);
+
+ boolean dabFmSoftLinking =
+ mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_DAB_FM_SOFT_LINKING);
+
+ assertWithMessage("DAB-FM soft linking config flag")
+ .that(dabFmSoftLinking).isTrue();
+ }
+
+ @Test
+ public void setConfigFlag_forTunerAdapter() throws Exception {
+ boolean dabFmLinking = true;
+
+ mRadioTuner.setConfigFlag(RadioManager.CONFIG_DAB_FM_LINKING, dabFmLinking);
+
+ verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_DAB_FM_LINKING, dabFmLinking);
+ }
+
+ @Test
+ public void getParameters_forTunerAdapter() throws Exception {
+ List<String> parameterKeys = Arrays.asList("ParameterKeyMock");
+ Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
+ when(mTunerMock.getParameters(parameterKeys)).thenReturn(parameters);
+
+ assertWithMessage("Parameters obtained from radio tuner")
+ .that(mRadioTuner.getParameters(parameterKeys)).isEqualTo(parameters);
+ }
+
+ @Test
+ public void setParameters_forTunerAdapter() throws Exception {
+ Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
+ when(mTunerMock.setParameters(parameters)).thenReturn(parameters);
+
+ assertWithMessage("Parameters set for radio tuner")
+ .that(mRadioTuner.setParameters(parameters)).isEqualTo(parameters);
+ }
+
+ @Test
+ public void isAntennaConnected_forTunerAdapter() throws Exception {
+ mTunerCallback.onAntennaState(/* connected= */ false);
+
+ assertWithMessage("Antenna connection status")
+ .that(mRadioTuner.isAntennaConnected()).isFalse();
+ }
+
+ @Test
+ public void hasControl_forTunerAdapter() throws Exception {
+ when(mTunerMock.isClosed()).thenReturn(true);
+
+ assertWithMessage("Control on tuner").that(mRadioTuner.hasControl()).isFalse();
+ }
+
+ @Test
+ public void onConfigurationChanged_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onConfigurationChanged(TEST_BAND_CONFIG);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onConfigurationChanged(TEST_BAND_CONFIG);
+ }
+
+ @Test
+ public void onTrafficAnnouncement_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onTrafficAnnouncement(/* active= */ true);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onTrafficAnnouncement(/* active= */ true);
+ }
+
+ @Test
+ public void onEmergencyAnnouncement_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onEmergencyAnnouncement(/* active= */ true);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onEmergencyAnnouncement(/* active= */ true);
+ }
+
+ @Test
+ public void onBackgroundScanAvailabilityChange_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onBackgroundScanAvailabilityChange(/* isAvailable= */ false);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onBackgroundScanAvailabilityChange(/* isAvailable= */ false);
+ }
+
+ @Test
+ public void onProgramListChanged_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onProgramListChanged();
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramListChanged();
+ }
+
+ @Test
+ public void onParametersUpdated_forTunerCallbackAdapter() throws Exception {
+ Map<String, String> parametersExpected = Map.of("ParameterKeyMock", "ParameterValueMock");
+
+ mTunerCallback.onParametersUpdated(parametersExpected);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onParametersUpdated(parametersExpected);
+ }
+
+ private static RadioManager.ProgramInfo createFmProgramInfo() {
+ return new RadioManager.ProgramInfo(FM_SELECTOR, FM_IDENTIFIER, FM_IDENTIFIER,
+ /* relatedContent= */ null, /* infoFlags= */ 0b110001,
+ /* signalQuality= */ 1, createRadioMetadata(), /* vendorInfo= */ null);
+ }
+
+ private static RadioManager.FmBandConfig createBandConfig() {
+ return new RadioManager.FmBandConfig(new RadioManager.FmBandDescriptor(
+ RadioManager.REGION_ITU_1, RadioManager.BAND_FM, /* lowerLimit= */ 87500,
+ /* upperLimit= */ 108000, /* spacing= */ 200, /* stereo= */ true,
+ /* rds= */ false, /* ta= */ false, /* af= */ false, /* es= */ false));
+ }
+
+ private static RadioMetadata createRadioMetadata() {
+ RadioMetadata.Builder metadataBuilder = new RadioMetadata.Builder();
+ return metadataBuilder.putString(RadioMetadata.METADATA_KEY_ARTIST, "artistMock").build();
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
index e2556d67..2cb058b 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
@@ -15,9 +15,11 @@
*/
package com.android.server.broadcastradio.aidl;
+import android.hardware.broadcastradio.IdentifierType;
import android.hardware.broadcastradio.Metadata;
import android.hardware.broadcastradio.ProgramIdentifier;
import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.VendorKeyValue;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
@@ -42,7 +44,7 @@
return makeProgramInfo(selector, signalQuality);
}
- static ProgramSelector makeFMSelector(long freq) {
+ static ProgramSelector makeFmSelector(long freq) {
return makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
freq));
@@ -54,6 +56,18 @@
/* vendorIds= */ null);
}
+ static android.hardware.broadcastradio.ProgramSelector makeHalFmSelector(int freq) {
+ ProgramIdentifier halId = new ProgramIdentifier();
+ halId.type = IdentifierType.AMFM_FREQUENCY_KHZ;
+ halId.value = freq;
+
+ android.hardware.broadcastradio.ProgramSelector halSelector =
+ new android.hardware.broadcastradio.ProgramSelector();
+ halSelector.primaryId = halId;
+ halSelector.secondaryIds = new ProgramIdentifier[0];
+ return halSelector;
+ }
+
static ProgramInfo programInfoToHalProgramInfo(RadioManager.ProgramInfo info) {
// Note that because ConversionUtils does not by design provide functions for all
// conversions, this function only copies fields that are set by makeProgramInfo().
@@ -69,7 +83,7 @@
return hwInfo;
}
- static ProgramInfo makeHalProgramSelector(
+ static ProgramInfo makeHalProgramInfo(
android.hardware.broadcastradio.ProgramSelector hwSel, int hwSignalQuality) {
ProgramInfo hwInfo = new ProgramInfo();
hwInfo.selector = hwSel;
@@ -80,4 +94,21 @@
hwInfo.metadata = new Metadata[]{};
return hwInfo;
}
+
+ static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) {
+ VendorKeyValue vendorKeyValue = new VendorKeyValue();
+ vendorKeyValue.key = vendorKey;
+ vendorKeyValue.value = vendorValue;
+ return vendorKeyValue;
+ }
+
+ static android.hardware.broadcastradio.Announcement makeAnnouncement(int type,
+ int selectorFreq) {
+ android.hardware.broadcastradio.Announcement halAnnouncement =
+ new android.hardware.broadcastradio.Announcement();
+ halAnnouncement.type = (byte) type;
+ halAnnouncement.selector = makeHalFmSelector(selectorFreq);
+ halAnnouncement.vendorInfo = new VendorKeyValue[]{};
+ return halAnnouncement;
+ }
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java
new file mode 100644
index 0000000..699212a
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 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.broadcastradio.aidl;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for AIDL HAL AnnouncementAggregator.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class AnnouncementAggregatorTest {
+ private static final int[] TEST_ENABLED_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+
+ private final Object mLock = new Object();
+ private AnnouncementAggregator mAnnouncementAggregator;
+ private IBinder.DeathRecipient mDeathRecipient;
+
+ @Mock
+ private IAnnouncementListener mListenerMock;
+ @Mock
+ private IBinder mBinderMock;
+ // Array of mocked radio modules
+ private RadioModule[] mRadioModuleMocks;
+ // Array of mocked close handles
+ private ICloseHandle[] mCloseHandleMocks;
+ // Array of mocked announcements
+ private Announcement[] mAnnouncementMocks;
+
+ @Before
+ public void setUp() throws Exception {
+ ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor =
+ ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
+ when(mListenerMock.asBinder()).thenReturn(mBinderMock);
+
+ mAnnouncementAggregator = new AnnouncementAggregator(mListenerMock, mLock);
+
+ verify(mBinderMock).linkToDeath(deathRecipientCaptor.capture(), anyInt());
+ mDeathRecipient = deathRecipientCaptor.getValue();
+ }
+
+ @Test
+ public void onListUpdated_withOneModuleWatcher() throws Exception {
+ ArgumentCaptor<IAnnouncementListener> moduleWatcherCaptor =
+ ArgumentCaptor.forClass(IAnnouncementListener.class);
+ watchModules(/* moduleNumber= */ 1);
+
+ verify(mRadioModuleMocks[0]).addAnnouncementListener(moduleWatcherCaptor.capture(), any());
+
+ moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[0]));
+
+ verify(mListenerMock).onListUpdated(any());
+ }
+
+ @Test
+ public void onListUpdated_withMultipleModuleWatchers() throws Exception {
+ int moduleNumber = 3;
+ watchModules(moduleNumber);
+
+ for (int index = 0; index < moduleNumber; index++) {
+ ArgumentCaptor<IAnnouncementListener> moduleWatcherCaptor =
+ ArgumentCaptor.forClass(IAnnouncementListener.class);
+ ArgumentCaptor<List<Announcement>> announcementsCaptor =
+ ArgumentCaptor.forClass(List.class);
+ verify(mRadioModuleMocks[index])
+ .addAnnouncementListener(moduleWatcherCaptor.capture(), any());
+
+ moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[index]));
+
+ verify(mListenerMock, times(index + 1)).onListUpdated(announcementsCaptor.capture());
+ assertWithMessage("Number of announcements %s", announcementsCaptor.getValue())
+ .that(announcementsCaptor.getValue().size()).isEqualTo(index + 1);
+ }
+ }
+
+ @Test
+ public void close_withOneModuleWatcher_invokesCloseHandle() throws Exception {
+ watchModules(/* moduleNumber= */ 1);
+
+ mAnnouncementAggregator.close();
+
+ verify(mCloseHandleMocks[0]).close();
+ verify(mBinderMock).unlinkToDeath(eq(mDeathRecipient), anyInt());
+ }
+
+ @Test
+ public void close_withMultipleModuleWatcher_invokesCloseHandles() throws Exception {
+ int moduleNumber = 3;
+ watchModules(moduleNumber);
+
+ mAnnouncementAggregator.close();
+
+ for (int index = 0; index < moduleNumber; index++) {
+ verify(mCloseHandleMocks[index]).close();
+ }
+ }
+
+ @Test
+ public void close_twice_invokesCloseHandleOnce() throws Exception {
+ watchModules(/* moduleNumber= */ 1);
+
+ mAnnouncementAggregator.close();
+ mAnnouncementAggregator.close();
+
+ verify(mCloseHandleMocks[0]).close();
+ verify(mBinderMock).unlinkToDeath(eq(mDeathRecipient), anyInt());
+ }
+
+ @Test
+ public void binderDied_forDeathRecipient_invokesCloseHandle() throws Exception {
+ watchModules(/* moduleNumber= */ 1);
+
+ mDeathRecipient.binderDied();
+
+ verify(mCloseHandleMocks[0]).close();
+
+ }
+
+ private void watchModules(int moduleNumber) throws RemoteException {
+ mRadioModuleMocks = new RadioModule[moduleNumber];
+ mCloseHandleMocks = new ICloseHandle[moduleNumber];
+ mAnnouncementMocks = new Announcement[moduleNumber];
+
+ for (int index = 0; index < moduleNumber; index++) {
+ mRadioModuleMocks[index] = mock(RadioModule.class);
+ mCloseHandleMocks[index] = mock(ICloseHandle.class);
+ mAnnouncementMocks[index] = mock(Announcement.class);
+
+ when(mRadioModuleMocks[index].addAnnouncementListener(any(), any()))
+ .thenReturn(mCloseHandleMocks[index]);
+ mAnnouncementAggregator.watchModule(mRadioModuleMocks[index], TEST_ENABLED_TYPES);
+ }
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
index 7f71921..cd1cd7e 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
@@ -21,11 +21,16 @@
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.graphics.Bitmap;
import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
import android.hardware.radio.RadioManager;
import android.os.RemoteException;
@@ -41,13 +46,20 @@
@RunWith(MockitoJUnitRunner.class)
public final class RadioModuleTest {
+ private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EVENT;
+
// Mocks
@Mock
private IBroadcastRadio mBroadcastRadioMock;
+ @Mock
+ private IAnnouncementListener mListenerMock;
+ @Mock
+ private android.hardware.broadcastradio.ICloseHandle mHalCloseHandleMock;
private final Object mLock = new Object();
// RadioModule under test
private RadioModule mRadioModule;
+ private android.hardware.broadcastradio.IAnnouncementListener mHalListener;
@Before
public void setup() throws RemoteException {
@@ -62,6 +74,11 @@
// TODO(b/241118988): test non-null image for getImage method
when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null);
+ doAnswer(invocation -> {
+ mHalListener = (android.hardware.broadcastradio.IAnnouncementListener) invocation
+ .getArguments()[0];
+ return null;
+ }).when(mBroadcastRadioMock).registerAnnouncementListener(any(), any());
}
@Test
@@ -71,7 +88,7 @@
}
@Test
- public void setInternalHalCallback_callbackSetInHal() throws RemoteException {
+ public void setInternalHalCallback_callbackSetInHal() throws Exception {
mRadioModule.setInternalHalCallback();
verify(mBroadcastRadioMock).setTunerCallback(any());
@@ -97,4 +114,36 @@
assertWithMessage("Exception for getting image with invalid ID")
.that(thrown).hasMessageThat().contains("Image ID is missing");
}
+
+ @Test
+ public void addAnnouncementListener_listenerRegistered() throws Exception {
+ mRadioModule.addAnnouncementListener(mListenerMock, new int[]{TEST_ENABLED_TYPE});
+
+ verify(mBroadcastRadioMock)
+ .registerAnnouncementListener(any(), eq(new byte[]{TEST_ENABLED_TYPE}));
+ }
+
+ @Test
+ public void onListUpdate_forAnnouncementListener() throws Exception {
+ android.hardware.broadcastradio.Announcement halAnnouncement =
+ AidlTestUtils.makeAnnouncement(TEST_ENABLED_TYPE, /* selectorFreq= */ 96300);
+ mRadioModule.addAnnouncementListener(mListenerMock, new int[]{TEST_ENABLED_TYPE});
+
+ mHalListener.onListUpdated(
+ new android.hardware.broadcastradio.Announcement[]{halAnnouncement});
+
+ verify(mListenerMock).onListUpdated(any());
+ }
+
+ @Test
+ public void close_forCloseHandle() throws Exception {
+ when(mBroadcastRadioMock.registerAnnouncementListener(any(), any()))
+ .thenReturn(mHalCloseHandleMock);
+ ICloseHandle closeHandle =
+ mRadioModule.addAnnouncementListener(mListenerMock, new int[]{TEST_ENABLED_TYPE});
+
+ closeHandle.close();
+
+ verify(mHalCloseHandleMock).close();
+ }
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 8354ad1..06d7cdd 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -22,6 +22,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
@@ -31,17 +32,19 @@
import android.graphics.Bitmap;
import android.hardware.broadcastradio.IBroadcastRadio;
import android.hardware.broadcastradio.ITunerCallback;
+import android.hardware.broadcastradio.IdentifierType;
import android.hardware.broadcastradio.ProgramInfo;
import android.hardware.broadcastradio.Result;
+import android.hardware.broadcastradio.VendorKeyValue;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
-import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.ArrayMap;
import android.util.ArraySet;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,19 +61,20 @@
*/
@RunWith(MockitoJUnitRunner.class)
public final class TunerSessionTest {
+
private static final VerificationWithTimeout CALLBACK_TIMEOUT =
timeout(/* millis= */ 200);
-
- private final int mSignalQuality = 1;
- private final long mAmfmFrequencySpacing = 500;
- private final long[] mAmfmFrequencyList = {97500, 98100, 99100};
- private final RadioManager.FmBandDescriptor mFmBandDescriptor =
+ private static final int SIGNAL_QUALITY = 1;
+ private static final long AM_FM_FREQUENCY_SPACING = 500;
+ private static final long[] AM_FM_FREQUENCY_LIST = {97500, 98100, 99100};
+ private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
/* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 100,
/* stereo= */ false, /* rds= */ false, /* ta= */ false, /* af= */ false,
/* ea= */ false);
- private final RadioManager.BandConfig mFmBandConfig =
- new RadioManager.FmBandConfig(mFmBandDescriptor);
+ private static final RadioManager.BandConfig FM_BAND_CONFIG =
+ new RadioManager.FmBandConfig(FM_BAND_DESCRIPTOR);
+ private static final int UNSUPPORTED_CONFIG_FLAG = 0;
// Mocks
@Mock private IBroadcastRadio mBroadcastRadioMock;
@@ -83,13 +87,12 @@
// Objects created by mRadioModule
private ITunerCallback mHalTunerCallback;
private ProgramInfo mHalCurrentInfo;
- private final int mUnsupportedConfigFlag = 0;
private final ArrayMap<Integer, Boolean> mHalConfigMap = new ArrayMap<>();
private TunerSession[] mTunerSessions;
@Before
- public void setup() throws RemoteException {
+ public void setup() throws Exception {
mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(
/* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
/* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
@@ -105,48 +108,58 @@
mRadioModule.setInternalHalCallback();
doAnswer(invocation -> {
- mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
- (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0],
- mSignalQuality);
+ android.hardware.broadcastradio.ProgramSelector halSel =
+ (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
+ if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
+ throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+ }
mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
- return null;
+ return Result.OK;
}).when(mBroadcastRadioMock).tune(any());
doAnswer(invocation -> {
if ((boolean) invocation.getArguments()[0]) {
- mHalCurrentInfo.selector.primaryId.value += mAmfmFrequencySpacing;
+ mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
} else {
- mHalCurrentInfo.selector.primaryId.value -= mAmfmFrequencySpacing;
+ mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
}
mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
- return null;
+ return Result.OK;
}).when(mBroadcastRadioMock).step(anyBoolean());
doAnswer(invocation -> {
+ if (mHalCurrentInfo == null) {
+ android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
+ AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
+
+ mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
+ return Result.OK;
+ }
mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
mHalCurrentInfo.selector.primaryId.value,
!(boolean) invocation.getArguments()[0]);
mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
- return null;
+ return Result.OK;
}).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null);
- mHalConfigMap.clear();
doAnswer(invocation -> {
int configFlag = (int) invocation.getArguments()[0];
- if (configFlag == mUnsupportedConfigFlag) {
+ if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
throw new ServiceSpecificException(Result.NOT_SUPPORTED);
}
return mHalConfigMap.getOrDefault(configFlag, false);
}).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
+
doAnswer(invocation -> {
int configFlag = (int) invocation.getArguments()[0];
- if (configFlag == mUnsupportedConfigFlag) {
+ if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
throw new ServiceSpecificException(Result.NOT_SUPPORTED);
}
mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
@@ -154,8 +167,13 @@
}).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
}
+ @After
+ public void cleanUp() {
+ mHalConfigMap.clear();
+ }
+
@Test
- public void openSession_withMultipleSessions() throws RemoteException {
+ public void openSession_withMultipleSessions() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
@@ -167,27 +185,27 @@
}
@Test
- public void setConfiguration() throws RemoteException {
+ public void setConfiguration() throws Exception {
openAidlClients(/* numClients= */ 1);
- mTunerSessions[0].setConfiguration(mFmBandConfig);
+ mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
- verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onConfigurationChanged(mFmBandConfig);
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onConfigurationChanged(FM_BAND_CONFIG);
}
@Test
- public void getConfiguration() throws RemoteException {
+ public void getConfiguration() throws Exception {
openAidlClients(/* numClients= */ 1);
- mTunerSessions[0].setConfiguration(mFmBandConfig);
+ mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
RadioManager.BandConfig config = mTunerSessions[0].getConfiguration();
assertWithMessage("Session configuration").that(config)
- .isEqualTo(mFmBandConfig);
+ .isEqualTo(FM_BAND_CONFIG);
}
@Test
- public void setMuted_withUnmuted() throws RemoteException {
+ public void setMuted_withUnmuted() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].setMuted(/* mute= */ false);
@@ -197,7 +215,7 @@
}
@Test
- public void setMuted_withMuted() throws RemoteException {
+ public void setMuted_withMuted() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].setMuted(/* mute= */ true);
@@ -207,7 +225,7 @@
}
@Test
- public void close_withOneSession() throws RemoteException {
+ public void close_withOneSession() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].close();
@@ -217,7 +235,7 @@
}
@Test
- public void close_withOnlyOneSession_withMultipleSessions() throws RemoteException {
+ public void close_withOnlyOneSession_withMultipleSessions() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
int closeIdx = 0;
@@ -238,7 +256,7 @@
}
@Test
- public void close_withOneSession_withError() throws RemoteException {
+ public void close_withOneSession_withError() throws Exception {
openAidlClients(/* numClients= */ 1);
int errorCode = RadioTuner.ERROR_SERVER_DIED;
@@ -250,7 +268,7 @@
}
@Test
- public void closeSessions_withMultipleSessions_withError() throws RemoteException {
+ public void closeSessions_withMultipleSessions_withError() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
@@ -265,11 +283,11 @@
}
@Test
- public void tune_withOneSession() throws RemoteException {
+ public void tune_withOneSession() throws Exception {
openAidlClients(/* numClients= */ 1);
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(mAmfmFrequencyList[1]);
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
RadioManager.ProgramInfo tuneInfo =
- AidlTestUtils.makeProgramInfo(initialSel, mSignalQuality);
+ AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
mTunerSessions[0].tune(initialSel);
@@ -277,12 +295,12 @@
}
@Test
- public void tune_withMultipleSessions() throws RemoteException {
+ public void tune_withMultipleSessions() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(mAmfmFrequencyList[1]);
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
RadioManager.ProgramInfo tuneInfo =
- AidlTestUtils.makeProgramInfo(initialSel, mSignalQuality);
+ AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
mTunerSessions[0].tune(initialSel);
@@ -293,15 +311,29 @@
}
@Test
- public void step_withDirectionUp() throws RemoteException {
- long initFreq = mAmfmFrequencyList[1];
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
- RadioManager.ProgramInfo stepUpInfo = AidlTestUtils.makeProgramInfo(
- AidlTestUtils.makeFMSelector(initFreq + mAmfmFrequencySpacing),
- mSignalQuality);
+ public void tune_withUnsupportedSelector_throwsException() throws Exception {
openAidlClients(/* numClients= */ 1);
- mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
- ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+ ProgramSelector unsupportedSelector = AidlTestUtils.makeProgramSelector(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300));
+
+ UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+ () -> mTunerSessions[0].tune(unsupportedSelector));
+
+ assertWithMessage("Exception for tuning on unsupported program selector")
+ .that(thrown).hasMessageThat().contains("tune: NOT_SUPPORTED");
+ }
+
+ @Test
+ public void step_withDirectionUp() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[1];
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
+ RadioManager.ProgramInfo stepUpInfo = AidlTestUtils.makeProgramInfo(
+ AidlTestUtils.makeFmSelector(initFreq + AM_FM_FREQUENCY_SPACING),
+ SIGNAL_QUALITY);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].step(/* directionDown= */ false, /* skipSubChannel= */ false);
@@ -310,15 +342,15 @@
}
@Test
- public void step_withDirectionDown() throws RemoteException {
- long initFreq = mAmfmFrequencyList[1];
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+ public void step_withDirectionDown() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[1];
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
RadioManager.ProgramInfo stepDownInfo = AidlTestUtils.makeProgramInfo(
- AidlTestUtils.makeFMSelector(initFreq - mAmfmFrequencySpacing),
- mSignalQuality);
+ AidlTestUtils.makeFmSelector(initFreq - AM_FM_FREQUENCY_SPACING),
+ SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
- mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
- ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
@@ -327,15 +359,15 @@
}
@Test
- public void scan_withDirectionUp() throws RemoteException {
- long initFreq = mAmfmFrequencyList[2];
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+ public void scan_withDirectionUp() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[2];
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
- AidlTestUtils.makeFMSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
- mSignalQuality);
+ AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
+ SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
- mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
- ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
@@ -344,15 +376,28 @@
}
@Test
- public void scan_withDirectionDown() throws RemoteException {
- long initFreq = mAmfmFrequencyList[2];
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+ public void scan_callsOnTuneFailedWhenTimeout() throws Exception {
+ int numSessions = 2;
+ openAidlClients(numSessions);
+
+ mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onTuneFailed(eq(Result.TIMEOUT), any());
+ }
+ }
+
+ @Test
+ public void scan_withDirectionDown() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[2];
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
- AidlTestUtils.makeFMSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
- mSignalQuality);
+ AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
+ SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
- mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
- ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
@@ -360,9 +405,9 @@
}
@Test
- public void cancel() throws RemoteException {
+ public void cancel() throws Exception {
openAidlClients(/* numClients= */ 1);
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(mAmfmFrequencyList[1]);
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
mTunerSessions[0].tune(initialSel);
mTunerSessions[0].cancel();
@@ -371,7 +416,7 @@
}
@Test
- public void getImage_withInvalidId_throwsIllegalArgumentException() throws RemoteException {
+ public void getImage_withInvalidId_throwsIllegalArgumentException() throws Exception {
openAidlClients(/* numClients= */ 1);
int imageId = IBroadcastRadio.INVALID_IMAGE;
@@ -384,7 +429,7 @@
}
@Test
- public void getImage_withValidId() throws RemoteException {
+ public void getImage_withValidId() throws Exception {
openAidlClients(/* numClients= */ 1);
int imageId = 1;
@@ -394,7 +439,7 @@
}
@Test
- public void startBackgroundScan() throws RemoteException {
+ public void startBackgroundScan() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].startBackgroundScan();
@@ -403,7 +448,7 @@
}
@Test
- public void stopProgramListUpdates() throws RemoteException {
+ public void stopProgramListUpdates() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
/* includeCategories= */ true, /* excludeModifications= */ false);
@@ -415,9 +460,9 @@
}
@Test
- public void isConfigFlagSupported_withUnsupportedFlag_returnsFalse() throws RemoteException {
+ public void isConfigFlagSupported_withUnsupportedFlag_returnsFalse() throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag;
+ int flag = UNSUPPORTED_CONFIG_FLAG;
boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
@@ -426,9 +471,9 @@
}
@Test
- public void isConfigFlagSupported_withSupportedFlag_returnsTrue() throws RemoteException {
+ public void isConfigFlagSupported_withSupportedFlag_returnsTrue() throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag + 1;
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
@@ -437,9 +482,9 @@
}
@Test
- public void setConfigFlag_withUnsupportedFlag_throwsRuntimeException() throws RemoteException {
+ public void setConfigFlag_withUnsupportedFlag_throwsRuntimeException() throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag;
+ int flag = UNSUPPORTED_CONFIG_FLAG;
RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
@@ -450,9 +495,9 @@
}
@Test
- public void setConfigFlag_withFlagSetToTrue() throws RemoteException {
+ public void setConfigFlag_withFlagSetToTrue() throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag + 1;
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
@@ -460,9 +505,9 @@
}
@Test
- public void setConfigFlag_withFlagSetToFalse() throws RemoteException {
+ public void setConfigFlag_withFlagSetToFalse() throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag + 1;
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
mTunerSessions[0].setConfigFlag(flag, /* value= */ false);
@@ -471,9 +516,9 @@
@Test
public void isConfigFlagSet_withUnsupportedFlag_throwsRuntimeException()
- throws RemoteException {
+ throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag;
+ int flag = UNSUPPORTED_CONFIG_FLAG;
RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
mTunerSessions[0].isConfigFlagSet(flag);
@@ -484,9 +529,9 @@
}
@Test
- public void isConfigFlagSet_withSupportedFlag() throws RemoteException {
+ public void isConfigFlagSet_withSupportedFlag() throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag + 1;
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
boolean expectedConfigFlagValue = true;
mTunerSessions[0].setConfigFlag(flag, /* value= */ expectedConfigFlagValue);
@@ -497,11 +542,10 @@
}
@Test
- public void setParameters_withMockParameters() throws RemoteException {
+ public void setParameters_withMockParameters() throws Exception {
openAidlClients(/* numClients= */ 1);
- Map<String, String> parametersSet = new ArrayMap<>();
- parametersSet.put("mockParam1", "mockValue1");
- parametersSet.put("mockParam2", "mockValue2");
+ Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
+ "mockParam2", "mockValue2");
mTunerSessions[0].setParameters(parametersSet);
@@ -510,7 +554,7 @@
}
@Test
- public void getParameters_withMockKeys() throws RemoteException {
+ public void getParameters_withMockKeys() throws Exception {
openAidlClients(/* numClients= */ 1);
List<String> parameterKeys = new ArrayList<>(2);
parameterKeys.add("mockKey1");
@@ -522,7 +566,36 @@
parameterKeys.toArray(new String[0]));
}
- private void openAidlClients(int numClients) throws RemoteException {
+ @Test
+ public void onConfigFlagUpdated_forTunerCallback() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+
+ mHalTunerCallback.onAntennaStateChange(/* connected= */ false);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onAntennaState(/* connected= */ false);
+ }
+ }
+
+ @Test
+ public void onParametersUpdated_forTunerCallback() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+ VendorKeyValue[] parametersUpdates = {
+ AidlTestUtils.makeVendorKeyValue("com.vendor.parameter1", "value1")};
+ Map<String, String> parametersExpected = Map.of("com.vendor.parameter1", "value1");
+
+ mHalTunerCallback.onParametersUpdated(parametersUpdates);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onParametersUpdated(parametersExpected);
+ }
+ }
+
+ private void openAidlClients(int numClients) throws Exception {
mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
mTunerSessions = new TunerSession[numClients];
for (int index = 0; index < numClients; index++) {
@@ -534,18 +607,18 @@
private long getSeekFrequency(long currentFrequency, boolean seekDown) {
long seekFrequency;
if (seekDown) {
- seekFrequency = mAmfmFrequencyList[mAmfmFrequencyList.length - 1];
- for (int i = mAmfmFrequencyList.length - 1; i >= 0; i--) {
- if (mAmfmFrequencyList[i] < currentFrequency) {
- seekFrequency = mAmfmFrequencyList[i];
+ seekFrequency = AM_FM_FREQUENCY_LIST[AM_FM_FREQUENCY_LIST.length - 1];
+ for (int i = AM_FM_FREQUENCY_LIST.length - 1; i >= 0; i--) {
+ if (AM_FM_FREQUENCY_LIST[i] < currentFrequency) {
+ seekFrequency = AM_FM_FREQUENCY_LIST[i];
break;
}
}
} else {
- seekFrequency = mAmfmFrequencyList[0];
- for (int index = 0; index < mAmfmFrequencyList.length; index++) {
- if (mAmfmFrequencyList[index] > currentFrequency) {
- seekFrequency = mAmfmFrequencyList[index];
+ seekFrequency = AM_FM_FREQUENCY_LIST[0];
+ for (int index = 0; index < AM_FM_FREQUENCY_LIST.length; index++) {
+ if (AM_FM_FREQUENCY_LIST[index] > currentFrequency) {
+ seekFrequency = AM_FM_FREQUENCY_LIST[index];
break;
}
}
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
new file mode 100644
index 0000000..67b24ec
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2022 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.backup;
+
+import static android.app.backup.BackupRestoreEventLogger.OperationType.BACKUP;
+import static android.app.backup.BackupRestoreEventLogger.OperationType.RESTORE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.fail;
+
+import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupRestoreEventLoggerTest {
+ private static final int DATA_TYPES_ALLOWED = 15;
+
+ private static final String DATA_TYPE_1 = "data_type_1";
+ private static final String DATA_TYPE_2 = "data_type_2";
+ private static final String ERROR_1 = "error_1";
+ private static final String ERROR_2 = "error_2";
+ private static final String METADATA_1 = "metadata_1";
+ private static final String METADATA_2 = "metadata_2";
+
+ private BackupRestoreEventLogger mLogger;
+ private MessageDigest mHashDigest;
+
+ @Before
+ public void setUp() throws Exception {
+ mHashDigest = MessageDigest.getInstance("SHA-256");
+ }
+
+ @Test
+ public void testBackupLogger_rejectsRestoreLogs() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ assertThat(mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5)).isFalse();
+ assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
+ assertThat(mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+ }
+
+ @Test
+ public void testRestoreLogger_rejectsBackupLogs() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ assertThat(mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5)).isFalse();
+ assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
+ assertThat(mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+ }
+
+ @Test
+ public void testBackupLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+ String dataType = DATA_TYPE_1 + i;
+ assertThat(mLogger.logItemsBackedUp(dataType, /* count */ 5)).isTrue();
+ assertThat(mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null))
+ .isTrue();
+ assertThat(mLogger.logBackupMetaData(dataType, METADATA_1)).isTrue();
+ }
+
+ assertThat(mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5)).isFalse();
+ assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
+ .isFalse();
+ assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+ }
+
+ @Test
+ public void testRestoreLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+ String dataType = DATA_TYPE_1 + i;
+ assertThat(mLogger.logItemsRestored(dataType, /* count */ 5)).isTrue();
+ assertThat(mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null))
+ .isTrue();
+ assertThat(mLogger.logRestoreMetadata(dataType, METADATA_1)).isTrue();
+ }
+
+ assertThat(mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5)).isFalse();
+ assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
+ .isFalse();
+ assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+ }
+
+ @Test
+ public void testLogBackupMetadata_repeatedCalls_recordsLatestMetadataHash() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_1);
+ mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_2);
+
+ byte[] recordedHash = getResultForDataType(mLogger, DATA_TYPE_1).getMetadataHash();
+ byte[] expectedHash = getMetaDataHash(METADATA_2);
+ assertThat(Arrays.equals(recordedHash, expectedHash)).isTrue();
+ }
+
+ @Test
+ public void testLogRestoreMetadata_repeatedCalls_recordsLatestMetadataHash() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_1);
+ mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_2);
+
+ byte[] recordedHash = getResultForDataType(mLogger, DATA_TYPE_1).getMetadataHash();
+ byte[] expectedHash = getMetaDataHash(METADATA_2);
+ assertThat(Arrays.equals(recordedHash, expectedHash)).isTrue();
+ }
+
+ @Test
+ public void testLogItemsBackedUp_repeatedCalls_recordsTotalItems() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsBackedUp(DATA_TYPE_1, firstCount);
+ mLogger.logItemsBackedUp(DATA_TYPE_1, secondCount);
+
+ int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+ assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+ }
+
+ @Test
+ public void testLogItemsRestored_repeatedCalls_recordsTotalItems() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsRestored(DATA_TYPE_1, firstCount);
+ mLogger.logItemsRestored(DATA_TYPE_1, secondCount);
+
+ int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+ assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+ }
+
+ @Test
+ public void testLogItemsBackedUp_multipleDataTypes_recordsEachDataType() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsBackedUp(DATA_TYPE_1, firstCount);
+ mLogger.logItemsBackedUp(DATA_TYPE_2, secondCount);
+
+ int firstDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+ int secondDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_2).getSuccessCount();
+ assertThat(firstDataTypeCount).isEqualTo(firstCount);
+ assertThat(secondDataTypeCount).isEqualTo(secondCount);
+ }
+
+ @Test
+ public void testLogItemsRestored_multipleDataTypes_recordsEachDataType() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsRestored(DATA_TYPE_1, firstCount);
+ mLogger.logItemsRestored(DATA_TYPE_2, secondCount);
+
+ int firstDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+ int secondDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_2).getSuccessCount();
+ assertThat(firstDataTypeCount).isEqualTo(firstCount);
+ assertThat(secondDataTypeCount).isEqualTo(secondCount);
+ }
+
+ @Test
+ public void testLogItemsBackupFailed_repeatedCalls_recordsTotalItems() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, /* error */ null);
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, "error");
+
+ int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getFailCount();
+ assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+ }
+
+ @Test
+ public void testLogItemsRestoreFailed_repeatedCalls_recordsTotalItems() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, /* error */ null);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, "error");
+
+ int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getFailCount();
+ assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+ }
+
+ @Test
+ public void testLogItemsBackupFailed_multipleErrors_recordsEachError() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, ERROR_1);
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, ERROR_2);
+
+ int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+ .getErrors().get(ERROR_1);
+ int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+ .getErrors().get(ERROR_2);
+ assertThat(firstErrorTypeCount).isEqualTo(firstCount);
+ assertThat(secondErrorTypeCount).isEqualTo(secondCount);
+ }
+
+ @Test
+ public void testLogItemsRestoreFailed_multipleErrors_recordsEachError() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, ERROR_1);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, ERROR_2);
+
+ int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+ .getErrors().get(ERROR_1);
+ int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+ .getErrors().get(ERROR_2);
+ assertThat(firstErrorTypeCount).isEqualTo(firstCount);
+ assertThat(secondErrorTypeCount).isEqualTo(secondCount);
+ }
+
+ private static DataTypeResult getResultForDataType(BackupRestoreEventLogger logger,
+ @BackupRestoreDataType String dataType) {
+ Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
+ if (result.isEmpty()) {
+ fail("Failed to find result for data type: " + dataType);
+ }
+ return result.get();
+ }
+
+ private static Optional<DataTypeResult> getResultForDataTypeIfPresent(
+ BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
+ List<DataTypeResult> resultList = logger.getLoggingResults();
+ return resultList.stream().filter(
+ dataTypeResult -> dataTypeResult.getDataType().equals(dataType)).findAny();
+ }
+
+ private byte[] getMetaDataHash(String metaData) {
+ return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/backup/OWNERS b/core/tests/coretests/src/android/app/backup/OWNERS
new file mode 100644
index 0000000..53b6c78
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/OWNERS
@@ -0,0 +1 @@
+include /services/backup/OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index fa4952e1..5553902 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -25,11 +25,12 @@
import android.test.AndroidTestCase;
import android.util.AttributeSet;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import androidx.test.filters.LargeTest;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
diff --git a/core/tests/coretests/src/android/hardware/display/BrightnessConfigurationTest.java b/core/tests/coretests/src/android/hardware/display/BrightnessConfigurationTest.java
index e750454..1c7ab74 100644
--- a/core/tests/coretests/src/android/hardware/display/BrightnessConfigurationTest.java
+++ b/core/tests/coretests/src/android/hardware/display/BrightnessConfigurationTest.java
@@ -23,13 +23,14 @@
import android.os.Parcel;
import android.util.Pair;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/tests/coretests/src/android/os/BundleMergerTest.java b/core/tests/coretests/src/android/os/BundleMergerTest.java
new file mode 100644
index 0000000..b7012ba
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BundleMergerTest.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static android.os.BundleMerger.STRATEGY_ARRAY_APPEND;
+import static android.os.BundleMerger.STRATEGY_ARRAY_LIST_APPEND;
+import static android.os.BundleMerger.STRATEGY_BOOLEAN_AND;
+import static android.os.BundleMerger.STRATEGY_BOOLEAN_OR;
+import static android.os.BundleMerger.STRATEGY_COMPARABLE_MAX;
+import static android.os.BundleMerger.STRATEGY_COMPARABLE_MIN;
+import static android.os.BundleMerger.STRATEGY_FIRST;
+import static android.os.BundleMerger.STRATEGY_LAST;
+import static android.os.BundleMerger.STRATEGY_NUMBER_ADD;
+import static android.os.BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST;
+import static android.os.BundleMerger.STRATEGY_REJECT;
+import static android.os.BundleMerger.merge;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.content.Intent;
+import android.net.Uri;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class BundleMergerTest {
+ /**
+ * Strategies are only applied when there is an actual conflict; in the
+ * absence of conflict we pick whichever value is defined.
+ */
+ @Test
+ public void testNoConflict() throws Exception {
+ for (int strategy = Byte.MIN_VALUE; strategy < Byte.MAX_VALUE; strategy++) {
+ assertEquals(null, merge(strategy, null, null));
+ assertEquals(10, merge(strategy, 10, null));
+ assertEquals(20, merge(strategy, null, 20));
+ }
+ }
+
+ /**
+ * Strategies are only applied to identical data types; if there are mixed
+ * types we always reject the two conflicting values.
+ */
+ @Test
+ public void testMixedTypes() throws Exception {
+ for (int strategy = Byte.MIN_VALUE; strategy < Byte.MAX_VALUE; strategy++) {
+ final int finalStrategy = strategy;
+ assertThrows(Exception.class, () -> {
+ merge(finalStrategy, 10, "foo");
+ });
+ assertThrows(Exception.class, () -> {
+ merge(finalStrategy, List.of("foo"), "bar");
+ });
+ assertThrows(Exception.class, () -> {
+ merge(finalStrategy, new String[] { "foo" }, "bar");
+ });
+ assertThrows(Exception.class, () -> {
+ merge(finalStrategy, Integer.valueOf(10), Long.valueOf(10));
+ });
+ }
+ }
+
+ @Test
+ public void testStrategyReject() throws Exception {
+ assertEquals(null, merge(STRATEGY_REJECT, 10, 20));
+
+ // Identical values aren't technically a conflict, so they're passed
+ // through without being rejected
+ assertEquals(10, merge(STRATEGY_REJECT, 10, 10));
+ assertArrayEquals(new int[] {10},
+ (int[]) merge(STRATEGY_REJECT, new int[] {10}, new int[] {10}));
+ }
+
+ @Test
+ public void testStrategyFirst() throws Exception {
+ assertEquals(10, merge(STRATEGY_FIRST, 10, 20));
+ }
+
+ @Test
+ public void testStrategyLast() throws Exception {
+ assertEquals(20, merge(STRATEGY_LAST, 10, 20));
+ }
+
+ @Test
+ public void testStrategyComparableMin() throws Exception {
+ assertEquals(10, merge(STRATEGY_COMPARABLE_MIN, 10, 20));
+ assertEquals(10, merge(STRATEGY_COMPARABLE_MIN, 20, 10));
+ assertEquals("a", merge(STRATEGY_COMPARABLE_MIN, "a", "z"));
+ assertEquals("a", merge(STRATEGY_COMPARABLE_MIN, "z", "a"));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_COMPARABLE_MIN, new Binder(), new Binder());
+ });
+ }
+
+ @Test
+ public void testStrategyComparableMax() throws Exception {
+ assertEquals(20, merge(STRATEGY_COMPARABLE_MAX, 10, 20));
+ assertEquals(20, merge(STRATEGY_COMPARABLE_MAX, 20, 10));
+ assertEquals("z", merge(STRATEGY_COMPARABLE_MAX, "a", "z"));
+ assertEquals("z", merge(STRATEGY_COMPARABLE_MAX, "z", "a"));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_COMPARABLE_MAX, new Binder(), new Binder());
+ });
+ }
+
+ @Test
+ public void testStrategyNumberAdd() throws Exception {
+ assertEquals(30, merge(STRATEGY_NUMBER_ADD, 10, 20));
+ assertEquals(30, merge(STRATEGY_NUMBER_ADD, 20, 10));
+ assertEquals(30L, merge(STRATEGY_NUMBER_ADD, 10L, 20L));
+ assertEquals(30L, merge(STRATEGY_NUMBER_ADD, 20L, 10L));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_NUMBER_ADD, new Binder(), new Binder());
+ });
+ }
+
+ @Test
+ public void testStrategyNumberIncrementFirst() throws Exception {
+ assertEquals(11, merge(STRATEGY_NUMBER_INCREMENT_FIRST, 10, 20));
+ assertEquals(21, merge(STRATEGY_NUMBER_INCREMENT_FIRST, 20, 10));
+ assertEquals(11L, merge(STRATEGY_NUMBER_INCREMENT_FIRST, 10L, 20L));
+ assertEquals(21L, merge(STRATEGY_NUMBER_INCREMENT_FIRST, 20L, 10L));
+ }
+
+ @Test
+ public void testStrategyBooleanAnd() throws Exception {
+ assertEquals(false, merge(STRATEGY_BOOLEAN_AND, false, false));
+ assertEquals(false, merge(STRATEGY_BOOLEAN_AND, true, false));
+ assertEquals(false, merge(STRATEGY_BOOLEAN_AND, false, true));
+ assertEquals(true, merge(STRATEGY_BOOLEAN_AND, true, true));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_BOOLEAN_AND, "True!", "False?");
+ });
+ }
+
+ @Test
+ public void testStrategyBooleanOr() throws Exception {
+ assertEquals(false, merge(STRATEGY_BOOLEAN_OR, false, false));
+ assertEquals(true, merge(STRATEGY_BOOLEAN_OR, true, false));
+ assertEquals(true, merge(STRATEGY_BOOLEAN_OR, false, true));
+ assertEquals(true, merge(STRATEGY_BOOLEAN_OR, true, true));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_BOOLEAN_OR, "True!", "False?");
+ });
+ }
+
+ @Test
+ public void testStrategyArrayAppend() throws Exception {
+ assertArrayEquals(new int[] {},
+ (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {}, new int[] {}));
+ assertArrayEquals(new int[] {10},
+ (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {10}, new int[] {}));
+ assertArrayEquals(new int[] {20},
+ (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {}, new int[] {20}));
+ assertArrayEquals(new int[] {10, 20},
+ (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {10}, new int[] {20}));
+ assertArrayEquals(new int[] {10, 30, 20, 40},
+ (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {10, 30}, new int[] {20, 40}));
+ assertArrayEquals(new String[] {"a", "b"},
+ (String[]) merge(STRATEGY_ARRAY_APPEND, new String[] {"a"}, new String[] {"b"}));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_ARRAY_APPEND, 10, 20);
+ });
+ }
+
+ @Test
+ public void testStrategyArrayListAppend() throws Exception {
+ assertEquals(arrayListOf(),
+ merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(), arrayListOf()));
+ assertEquals(arrayListOf(10),
+ merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(10), arrayListOf()));
+ assertEquals(arrayListOf(20),
+ merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(), arrayListOf(20)));
+ assertEquals(arrayListOf(10, 20),
+ merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(10), arrayListOf(20)));
+ assertEquals(arrayListOf(10, 30, 20, 40),
+ merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(10, 30), arrayListOf(20, 40)));
+ assertEquals(arrayListOf("a", "b"),
+ merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf("a"), arrayListOf("b")));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_ARRAY_LIST_APPEND, 10, 20);
+ });
+ }
+
+ @Test
+ public void testMerge_Simple() throws Exception {
+ final BundleMerger merger = new BundleMerger();
+ final Bundle probe = new Bundle();
+ probe.putInt(Intent.EXTRA_INDEX, 42);
+
+ assertEquals(null, merger.merge(null, null));
+ assertEquals(probe.keySet(), merger.merge(probe, null).keySet());
+ assertEquals(probe.keySet(), merger.merge(null, probe).keySet());
+ assertEquals(probe.keySet(), merger.merge(probe, probe).keySet());
+ }
+
+ /**
+ * Verify that we can merge parcelables present in the base classpath, since
+ * everyone on the device will be able to unpack them.
+ */
+ @Test
+ public void testMerge_Parcelable_BCP() throws Exception {
+ final BundleMerger merger = new BundleMerger();
+ merger.setMergeStrategy(Intent.EXTRA_STREAM, STRATEGY_COMPARABLE_MIN);
+
+ Bundle a = new Bundle();
+ a.putParcelable(Intent.EXTRA_STREAM, Uri.parse("http://example.com"));
+ a = parcelAndUnparcel(a);
+
+ Bundle b = new Bundle();
+ b.putParcelable(Intent.EXTRA_STREAM, Uri.parse("http://example.net"));
+ b = parcelAndUnparcel(b);
+
+ assertEquals(Uri.parse("http://example.com"),
+ merger.merge(a, b).getParcelable(Intent.EXTRA_STREAM, Uri.class));
+ assertEquals(Uri.parse("http://example.com"),
+ merger.merge(b, a).getParcelable(Intent.EXTRA_STREAM, Uri.class));
+ }
+
+ /**
+ * Verify that we tiptoe around custom parcelables while still merging other
+ * known data types. Custom parcelables aren't in the base classpath, so not
+ * everyone on the device will be able to unpack them.
+ */
+ @Test
+ public void testMerge_Parcelable_Custom() throws Exception {
+ final BundleMerger merger = new BundleMerger();
+ merger.setMergeStrategy(Intent.EXTRA_INDEX, STRATEGY_NUMBER_ADD);
+
+ Bundle a = new Bundle();
+ a.putInt(Intent.EXTRA_INDEX, 10);
+ a.putString(Intent.EXTRA_CC, "foo@bar.com");
+ a.putParcelable(Intent.EXTRA_SUBJECT, new ExplodingParcelable());
+ a = parcelAndUnparcel(a);
+
+ Bundle b = new Bundle();
+ b.putInt(Intent.EXTRA_INDEX, 20);
+ a.putString(Intent.EXTRA_BCC, "foo@baz.com");
+ b.putParcelable(Intent.EXTRA_STREAM, new ExplodingParcelable());
+ b = parcelAndUnparcel(b);
+
+ Bundle ab = merger.merge(a, b);
+ assertEquals(Set.of(Intent.EXTRA_INDEX, Intent.EXTRA_CC, Intent.EXTRA_BCC,
+ Intent.EXTRA_SUBJECT, Intent.EXTRA_STREAM), ab.keySet());
+ assertEquals(30, ab.getInt(Intent.EXTRA_INDEX));
+ assertEquals("foo@bar.com", ab.getString(Intent.EXTRA_CC));
+ assertEquals("foo@baz.com", ab.getString(Intent.EXTRA_BCC));
+
+ // And finally, make sure that if we try unpacking one of our custom
+ // values that we actually explode
+ assertThrows(BadParcelableException.class, () -> {
+ ab.getParcelable(Intent.EXTRA_SUBJECT, ExplodingParcelable.class);
+ });
+ assertThrows(BadParcelableException.class, () -> {
+ ab.getParcelable(Intent.EXTRA_STREAM, ExplodingParcelable.class);
+ });
+ }
+
+ @Test
+ public void testMerge_PackageChanged() throws Exception {
+ final BundleMerger merger = new BundleMerger();
+ merger.setMergeStrategy(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, STRATEGY_ARRAY_APPEND);
+
+ final Bundle first = new Bundle();
+ first.putInt(Intent.EXTRA_UID, 10001);
+ first.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, new String[] {
+ "com.example.Foo",
+ });
+
+ final Bundle second = new Bundle();
+ second.putInt(Intent.EXTRA_UID, 10001);
+ second.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, new String[] {
+ "com.example.Bar",
+ "com.example.Baz",
+ });
+
+ final Bundle res = merger.merge(first, second);
+ assertEquals(10001, res.getInt(Intent.EXTRA_UID));
+ assertArrayEquals(new String[] {
+ "com.example.Foo", "com.example.Bar", "com.example.Baz",
+ }, res.getStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
+ }
+
+ /**
+ * Each event in isolation reports "zero events dropped", but if we need to
+ * merge them together, then we start incrementing.
+ */
+ @Test
+ public void testMerge_DropBox() throws Exception {
+ final BundleMerger merger = new BundleMerger();
+ merger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
+ STRATEGY_COMPARABLE_MAX);
+ merger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
+ STRATEGY_NUMBER_INCREMENT_FIRST);
+
+ final long now = System.currentTimeMillis();
+ final Bundle a = new Bundle();
+ a.putString(DropBoxManager.EXTRA_TAG, "system_server_strictmode");
+ a.putLong(DropBoxManager.EXTRA_TIME, now);
+ a.putInt(DropBoxManager.EXTRA_DROPPED_COUNT, 0);
+
+ final Bundle b = new Bundle();
+ b.putString(DropBoxManager.EXTRA_TAG, "system_server_strictmode");
+ b.putLong(DropBoxManager.EXTRA_TIME, now + 1000);
+ b.putInt(DropBoxManager.EXTRA_DROPPED_COUNT, 0);
+
+ final Bundle c = new Bundle();
+ c.putString(DropBoxManager.EXTRA_TAG, "system_server_strictmode");
+ c.putLong(DropBoxManager.EXTRA_TIME, now + 2000);
+ c.putInt(DropBoxManager.EXTRA_DROPPED_COUNT, 0);
+
+ final Bundle ab = merger.merge(a, b);
+ assertEquals("system_server_strictmode", ab.getString(DropBoxManager.EXTRA_TAG));
+ assertEquals(now + 1000, ab.getLong(DropBoxManager.EXTRA_TIME));
+ assertEquals(1, ab.getInt(DropBoxManager.EXTRA_DROPPED_COUNT));
+
+ final Bundle abc = merger.merge(ab, c);
+ assertEquals("system_server_strictmode", abc.getString(DropBoxManager.EXTRA_TAG));
+ assertEquals(now + 2000, abc.getLong(DropBoxManager.EXTRA_TIME));
+ assertEquals(2, abc.getInt(DropBoxManager.EXTRA_DROPPED_COUNT));
+ }
+
+ private static ArrayList<Object> arrayListOf(Object... values) {
+ final ArrayList<Object> res = new ArrayList<>(values.length);
+ for (Object value : values) {
+ res.add(value);
+ }
+ return res;
+ }
+
+ private static Bundle parcelAndUnparcel(Bundle input) {
+ final Parcel parcel = Parcel.obtain();
+ try {
+ input.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return Bundle.CREATOR.createFromParcel(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ /**
+ * Object that only offers to parcel itself; if something tries unparceling
+ * it, it will "explode" by throwing an exception.
+ * <p>
+ * Useful for verifying interactions that must leave unknown data in a
+ * parceled state.
+ */
+ public static class ExplodingParcelable implements Parcelable {
+ public ExplodingParcelable() {
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(42);
+ }
+
+ public static final Creator<ExplodingParcelable> CREATOR =
+ new Creator<ExplodingParcelable>() {
+ @Override
+ public ExplodingParcelable createFromParcel(Parcel in) {
+ throw new BadParcelableException("exploding!");
+ }
+
+ @Override
+ public ExplodingParcelable[] newArray(int size) {
+ throw new BadParcelableException("exploding!");
+ }
+ };
+ }
+}
diff --git a/core/tests/coretests/src/android/util/BinaryXmlTest.java b/core/tests/coretests/src/android/util/BinaryXmlTest.java
index fd625dce..025e831 100644
--- a/core/tests/coretests/src/android/util/BinaryXmlTest.java
+++ b/core/tests/coretests/src/android/util/BinaryXmlTest.java
@@ -30,6 +30,9 @@
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/XmlTest.java b/core/tests/coretests/src/android/util/XmlTest.java
index 1cd4d13..91ebc2a 100644
--- a/core/tests/coretests/src/android/util/XmlTest.java
+++ b/core/tests/coretests/src/android/util/XmlTest.java
@@ -29,6 +29,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index cc68fce..5e12313 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -31,9 +31,12 @@
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.LAST_TYPE;
import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
+import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.defaultVisible;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -63,7 +66,7 @@
import android.platform.test.annotations.Presubmit;
import android.view.InsetsState.InternalInsetsType;
import android.view.SurfaceControl.Transaction;
-import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
@@ -245,7 +248,7 @@
mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(Type.ime(), true /* fromIme */);
+ mController.show(ime(), true /* fromIme */);
verify(loggingListener).onReady(notNull(), anyInt());
});
}
@@ -260,16 +263,16 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(Type.ime(), true /* fromIme */);
- mController.show(Type.all());
+ mController.show(ime(), true /* fromIme */);
+ mController.show(all());
// quickly jump to final state by cancelling it.
mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.hide(Type.ime(), true /* fromIme */);
- mController.hide(Type.all());
+ mController.hide(ime(), true /* fromIme */);
+ mController.hide(all());
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
@@ -285,10 +288,10 @@
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
- mController.show(Type.ime(), true /* fromIme */);
+ mController.show(ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.hide(Type.ime(), true /* fromIme */);
+ mController.hide(ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
@@ -304,7 +307,7 @@
InsetsSourceControl ime = controls[2];
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- int types = Type.navigationBars() | Type.systemBars();
+ int types = navigationBars() | systemBars();
// test hide select types.
mController.hide(types);
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
@@ -336,7 +339,7 @@
InsetsSourceControl ime = controls[2];
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- int types = Type.navigationBars() | Type.systemBars();
+ int types = navigationBars() | systemBars();
// test show select types.
mController.show(types);
mController.cancelExistingAnimations();
@@ -345,21 +348,21 @@
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test hide all
- mController.hide(Type.all());
+ mController.hide(all());
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test single show
- mController.show(Type.navigationBars());
+ mController.show(navigationBars());
mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test single hide
- mController.hide(Type.navigationBars());
+ mController.hide(navigationBars());
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
@@ -377,8 +380,8 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// start two animations and see if previous is cancelled and final state is reached.
- mController.hide(Type.navigationBars());
- mController.hide(Type.systemBars());
+ mController.hide(navigationBars());
+ mController.hide(systemBars());
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimations();
@@ -386,8 +389,8 @@
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.show(Type.navigationBars());
- mController.show(Type.systemBars());
+ mController.show(navigationBars());
+ mController.show(systemBars());
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimations();
@@ -395,10 +398,10 @@
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- int types = Type.navigationBars() | Type.systemBars();
+ int types = navigationBars() | systemBars();
// show two at a time and hide one by one.
mController.show(types);
- mController.hide(Type.navigationBars());
+ mController.hide(navigationBars());
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimations();
@@ -406,7 +409,7 @@
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.hide(Type.systemBars());
+ mController.hide(systemBars());
assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimations();
@@ -425,16 +428,16 @@
InsetsSourceControl ime = controls[2];
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- int types = Type.navigationBars() | Type.systemBars();
+ int types = navigationBars() | systemBars();
// show two at a time and hide one by one.
mController.show(types);
- mController.hide(Type.navigationBars());
+ mController.hide(navigationBars());
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.hide(Type.systemBars());
+ mController.hide(systemBars());
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
@@ -448,7 +451,7 @@
mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- mController.hide(Type.statusBars());
+ mController.hide(statusBars());
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
@@ -689,7 +692,7 @@
public void testResizeAnimation_insetsTypes() {
for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
final @AnimationType int expectedAnimationType =
- (InsetsState.toPublicType(type) & Type.systemBars()) != 0
+ (InsetsState.toPublicType(type) & systemBars()) != 0
? ANIMATION_TYPE_RESIZE
: ANIMATION_TYPE_NONE;
doTestResizeAnimation_insetsTypes(type, expectedAnimationType);
@@ -824,15 +827,13 @@
@Test
public void testRequestedState() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- final InsetsVisibilities request = mTestHost.getRequestedVisibilities();
-
mController.hide(statusBars() | navigationBars());
- assertFalse(request.getVisibility(ITYPE_STATUS_BAR));
- assertFalse(request.getVisibility(ITYPE_NAVIGATION_BAR));
+ assertFalse(mTestHost.isRequestedVisible(statusBars()));
+ assertFalse(mTestHost.isRequestedVisible(navigationBars()));
mController.show(statusBars() | navigationBars());
- assertTrue(request.getVisibility(ITYPE_STATUS_BAR));
- assertTrue(request.getVisibility(ITYPE_NAVIGATION_BAR));
+ assertTrue(mTestHost.isRequestedVisible(statusBars()));
+ assertTrue(mTestHost.isRequestedVisible(navigationBars()));
});
}
@@ -981,20 +982,20 @@
public static class TestHost extends ViewRootInsetsControllerHost {
- private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ private @InsetsType int mRequestedVisibleTypes = defaultVisible();
TestHost(ViewRootImpl viewRoot) {
super(viewRoot);
}
@Override
- public void updateRequestedVisibilities(InsetsVisibilities visibilities) {
- mRequestedVisibilities.set(visibilities);
- super.updateRequestedVisibilities(visibilities);
+ public void updateRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
+ mRequestedVisibleTypes = requestedVisibleTypes;
+ super.updateRequestedVisibleTypes(requestedVisibleTypes);
}
- public InsetsVisibilities getRequestedVisibilities() {
- return mRequestedVisibilities;
+ public boolean isRequestedVisible(@InsetsType int types) {
+ return (mRequestedVisibleTypes & types) != 0;
}
}
}
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java
index f5fcb03..297b07f 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java
@@ -20,8 +20,10 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -33,6 +35,7 @@
import java.util.Locale;
import java.util.Objects;
+import java.util.function.Supplier;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -41,28 +44,28 @@
public void verifyLocale(final String localeString) {
// InputMethodSubtype#getLocale() returns exactly the same string that is passed to the
// constructor.
- assertEquals(localeString, createDummySubtype(localeString).getLocale());
+ assertEquals(localeString, createSubtype(localeString).getLocale());
// InputMethodSubtype#getLocale() should be preserved via marshaling.
- assertEquals(createDummySubtype(localeString).getLocale(),
- cloneViaParcel(createDummySubtype(localeString)).getLocale());
+ assertEquals(createSubtype(localeString).getLocale(),
+ cloneViaParcel(createSubtype(localeString)).getLocale());
// InputMethodSubtype#getLocale() should be preserved via marshaling.
- assertEquals(createDummySubtype(localeString).getLocale(),
- cloneViaParcel(cloneViaParcel(createDummySubtype(localeString))).getLocale());
+ assertEquals(createSubtype(localeString).getLocale(),
+ cloneViaParcel(cloneViaParcel(createSubtype(localeString))).getLocale());
// Make sure InputMethodSubtype#hashCode() returns the same hash code.
- assertEquals(createDummySubtype(localeString).hashCode(),
- createDummySubtype(localeString).hashCode());
- assertEquals(createDummySubtype(localeString).hashCode(),
- cloneViaParcel(createDummySubtype(localeString)).hashCode());
- assertEquals(createDummySubtype(localeString).hashCode(),
- cloneViaParcel(cloneViaParcel(createDummySubtype(localeString))).hashCode());
+ assertEquals(createSubtype(localeString).hashCode(),
+ createSubtype(localeString).hashCode());
+ assertEquals(createSubtype(localeString).hashCode(),
+ cloneViaParcel(createSubtype(localeString)).hashCode());
+ assertEquals(createSubtype(localeString).hashCode(),
+ cloneViaParcel(cloneViaParcel(createSubtype(localeString))).hashCode());
}
@Test
public void testLocaleObj_locale() {
- final InputMethodSubtype usSubtype = createDummySubtype("en_US");
+ final InputMethodSubtype usSubtype = createSubtype("en_US");
Locale localeObject = usSubtype.getLocaleObject();
assertEquals("en", localeObject.getLanguage());
assertEquals("US", localeObject.getCountry());
@@ -73,7 +76,7 @@
@Test
public void testLocaleObj_languageTag() {
- final InputMethodSubtype usSubtype = createDummySubtypeUsingLanguageTag("en-US");
+ final InputMethodSubtype usSubtype = createSubtypeUsingLanguageTag("en-US");
Locale localeObject = usSubtype.getLocaleObject();
assertNotNull(localeObject);
assertEquals("en", localeObject.getLanguage());
@@ -85,7 +88,7 @@
@Test
public void testLocaleObj_emptyLocale() {
- final InputMethodSubtype emptyLocaleSubtype = createDummySubtype("");
+ final InputMethodSubtype emptyLocaleSubtype = createSubtype("");
assertNull(emptyLocaleSubtype.getLocaleObject());
// It should continue returning null when called multiple times.
assertNull(emptyLocaleSubtype.getLocaleObject());
@@ -110,8 +113,8 @@
@Test
public void testDeprecatedLocaleString() throws Exception {
// Make sure "iw" is not automatically replaced with "he".
- final InputMethodSubtype subtypeIw = createDummySubtype("iw");
- final InputMethodSubtype subtypeHe = createDummySubtype("he");
+ final InputMethodSubtype subtypeIw = createSubtype("iw");
+ final InputMethodSubtype subtypeHe = createSubtype("he");
assertEquals("iw", subtypeIw.getLocale());
assertEquals("he", subtypeHe.getLocale());
assertFalse(Objects.equals(subtypeIw, subtypeHe));
@@ -125,6 +128,64 @@
assertEquals("he", clonedSubtypeHe.getLocale());
}
+ @Test
+ public void testCanonicalizedLanguageTagObjectCache() {
+ final InputMethodSubtype subtype = createSubtypeUsingLanguageTag("en-US");
+ // Verify that the returned object is cached and any subsequent call should return the same
+ // object, which is strictly guaranteed if the method gets called only on a single thread.
+ assertSame(subtype.getCanonicalizedLanguageTag(), subtype.getCanonicalizedLanguageTag());
+ }
+
+ @Test
+ public void testCanonicalizedLanguageTag() {
+ verifyCanonicalizedLanguageTag("en", "en");
+ verifyCanonicalizedLanguageTag("en-US", "en-US");
+ verifyCanonicalizedLanguageTag("en-Latn-US-t-k0-qwerty", "en-Latn-US-t-k0-qwerty");
+
+ verifyCanonicalizedLanguageTag("en-us", "en-US");
+ verifyCanonicalizedLanguageTag("EN-us", "en-US");
+
+ verifyCanonicalizedLanguageTag(null, "");
+ verifyCanonicalizedLanguageTag("", "");
+
+ verifyCanonicalizedLanguageTag("und", "und");
+ verifyCanonicalizedLanguageTag("apparently invalid language tag!!!", "und");
+ }
+
+ private void verifyCanonicalizedLanguageTag(
+ @Nullable String languageTag, @Nullable String expectedLanguageTag) {
+ final InputMethodSubtype subtype = createSubtypeUsingLanguageTag(languageTag);
+ assertEquals(subtype.getCanonicalizedLanguageTag(), expectedLanguageTag);
+ }
+
+ @Test
+ public void testIsSuitableForPhysicalKeyboardLayoutMapping() {
+ final Supplier<InputMethodSubtypeBuilder> getValidBuilder = () ->
+ new InputMethodSubtypeBuilder()
+ .setLanguageTag("en-US")
+ .setIsAuxiliary(false)
+ .setSubtypeMode("keyboard")
+ .setSubtypeId(1);
+
+ assertTrue(getValidBuilder.get().build().isSuitableForPhysicalKeyboardLayoutMapping());
+
+ // mode == "voice" is not suitable.
+ assertFalse(getValidBuilder.get().setSubtypeMode("voice").build()
+ .isSuitableForPhysicalKeyboardLayoutMapping());
+
+ // Auxiliary subtype not suitable.
+ assertFalse(getValidBuilder.get().setIsAuxiliary(true).build()
+ .isSuitableForPhysicalKeyboardLayoutMapping());
+
+ // languageTag == null is not suitable.
+ assertFalse(getValidBuilder.get().setLanguageTag(null).build()
+ .isSuitableForPhysicalKeyboardLayoutMapping());
+
+ // languageTag == "und" is not suitable.
+ assertFalse(getValidBuilder.get().setLanguageTag("und").build()
+ .isSuitableForPhysicalKeyboardLayoutMapping());
+ }
+
private static InputMethodSubtype cloneViaParcel(final InputMethodSubtype original) {
Parcel parcel = null;
try {
@@ -139,7 +200,7 @@
}
}
- private static InputMethodSubtype createDummySubtype(final String locale) {
+ private static InputMethodSubtype createSubtype(final String locale) {
final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
return builder.setSubtypeNameResId(0)
.setSubtypeIconResId(0)
@@ -148,7 +209,7 @@
.build();
}
- private static InputMethodSubtype createDummySubtypeUsingLanguageTag(
+ private static InputMethodSubtype createSubtypeUsingLanguageTag(
final String languageTag) {
final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
return builder.setSubtypeNameResId(0)
diff --git a/core/tests/coretests/src/com/android/internal/util/FastDataTest.java b/core/tests/coretests/src/com/android/internal/util/FastDataTest.java
index 04dfd6e..de325ab 100644
--- a/core/tests/coretests/src/com/android/internal/util/FastDataTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/FastDataTest.java
@@ -23,6 +23,9 @@
import android.annotation.NonNull;
import android.util.ExceptionUtils;
+import com.android.modules.utils.FastDataInput;
+import com.android.modules.utils.FastDataOutput;
+
import libcore.util.HexEncoding;
import org.junit.Assume;
@@ -39,6 +42,8 @@
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
@@ -61,14 +66,32 @@
this.use4ByteSequence = use4ByteSequence;
}
+ @NonNull
+ private FastDataInput createFastDataInput(@NonNull InputStream in, int bufferSize) {
+ if (use4ByteSequence) {
+ return new ArtFastDataInput(in, bufferSize);
+ } else {
+ return new FastDataInput(in, bufferSize);
+ }
+ }
+
+ @NonNull
+ private FastDataOutput createFastDataOutput(@NonNull OutputStream out, int bufferSize) {
+ if (use4ByteSequence) {
+ return new ArtFastDataOutput(out, bufferSize);
+ } else {
+ return new FastDataOutput(out, bufferSize);
+ }
+ }
+
@Test
public void testEndOfFile_Int() throws Exception {
- try (FastDataInput in = new FastDataInput(new ByteArrayInputStream(
- new byte[] { 1 }), 1000, use4ByteSequence)) {
+ try (FastDataInput in = createFastDataInput(new ByteArrayInputStream(
+ new byte[] { 1 }), 1000)) {
assertThrows(EOFException.class, () -> in.readInt());
}
- try (FastDataInput in = new FastDataInput(new ByteArrayInputStream(
- new byte[] { 1, 1, 1, 1 }), 1000, use4ByteSequence)) {
+ try (FastDataInput in = createFastDataInput(new ByteArrayInputStream(
+ new byte[] { 1, 1, 1, 1 }), 1000)) {
assertEquals(1, in.readByte());
assertThrows(EOFException.class, () -> in.readInt());
}
@@ -76,25 +99,25 @@
@Test
public void testEndOfFile_String() throws Exception {
- try (FastDataInput in = new FastDataInput(new ByteArrayInputStream(
- new byte[] { 1 }), 1000, use4ByteSequence)) {
+ try (FastDataInput in = createFastDataInput(new ByteArrayInputStream(
+ new byte[] { 1 }), 1000)) {
assertThrows(EOFException.class, () -> in.readUTF());
}
- try (FastDataInput in = new FastDataInput(new ByteArrayInputStream(
- new byte[] { 1, 1, 1, 1 }), 1000, use4ByteSequence)) {
+ try (FastDataInput in = createFastDataInput(new ByteArrayInputStream(
+ new byte[] { 1, 1, 1, 1 }), 1000)) {
assertThrows(EOFException.class, () -> in.readUTF());
}
}
@Test
public void testEndOfFile_Bytes_Small() throws Exception {
- try (FastDataInput in = new FastDataInput(new ByteArrayInputStream(
- new byte[] { 1, 1, 1, 1 }), 1000, use4ByteSequence)) {
+ try (FastDataInput in = createFastDataInput(new ByteArrayInputStream(
+ new byte[] { 1, 1, 1, 1 }), 1000)) {
final byte[] tmp = new byte[10];
assertThrows(EOFException.class, () -> in.readFully(tmp));
}
- try (FastDataInput in = new FastDataInput(new ByteArrayInputStream(
- new byte[] { 1, 1, 1, 1 }), 1000, use4ByteSequence)) {
+ try (FastDataInput in = createFastDataInput(new ByteArrayInputStream(
+ new byte[] { 1, 1, 1, 1 }), 1000)) {
final byte[] tmp = new byte[10_000];
assertThrows(EOFException.class, () -> in.readFully(tmp));
}
@@ -103,8 +126,7 @@
@Test
public void testUTF_Bounds() throws Exception {
final char[] buf = new char[65_534];
- try (FastDataOutput out = new FastDataOutput(new ByteArrayOutputStream(),
- BOUNCE_SIZE, use4ByteSequence)) {
+ try (FastDataOutput out = createFastDataOutput(new ByteArrayOutputStream(), BOUNCE_SIZE)) {
// Writing simple string will fit fine
Arrays.fill(buf, '!');
final String simple = new String(buf);
@@ -132,17 +154,15 @@
doTranscodeWrite(out);
out.flush();
- final FastDataInput in = new FastDataInput(
- new ByteArrayInputStream(outStream.toByteArray()),
- BOUNCE_SIZE, use4ByteSequence);
+ final FastDataInput in = createFastDataInput(
+ new ByteArrayInputStream(outStream.toByteArray()), BOUNCE_SIZE);
doTranscodeRead(in);
}
// Verify that fast data can be read by upstream
{
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- final FastDataOutput out = new FastDataOutput(outStream,
- BOUNCE_SIZE, use4ByteSequence);
+ final FastDataOutput out = createFastDataOutput(outStream, BOUNCE_SIZE);
doTranscodeWrite(out);
out.flush();
@@ -299,7 +319,7 @@
final DataOutput slowData = new DataOutputStream(slowStream);
final ByteArrayOutputStream fastStream = new ByteArrayOutputStream();
- final FastDataOutput fastData = FastDataOutput.obtainUsing3ByteSequences(fastStream);
+ final FastDataOutput fastData = FastDataOutput.obtain(fastStream);
for (int cp = Character.MIN_CODE_POINT; cp < Character.MAX_CODE_POINT; cp++) {
if (Character.isValidCodePoint(cp)) {
@@ -416,16 +436,14 @@
private void doBounce(@NonNull ThrowingConsumer<FastDataOutput> out,
@NonNull ThrowingConsumer<FastDataInput> in, int count) throws Exception {
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- final FastDataOutput outData = new FastDataOutput(outStream,
- BOUNCE_SIZE, use4ByteSequence);
+ final FastDataOutput outData = createFastDataOutput(outStream, BOUNCE_SIZE);
for (int i = 0; i < count; i++) {
out.accept(outData);
}
outData.flush();
final ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
- final FastDataInput inData = new FastDataInput(inStream,
- BOUNCE_SIZE, use4ByteSequence);
+ final FastDataInput inData = createFastDataInput(inStream, BOUNCE_SIZE);
for (int i = 0; i < count; i++) {
in.accept(inData);
}
diff --git a/core/tests/utiltests/src/com/android/internal/util/XmlUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/XmlUtilsTest.java
index 06e9759..0484068 100644
--- a/core/tests/utiltests/src/com/android/internal/util/XmlUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/XmlUtilsTest.java
@@ -18,10 +18,11 @@
import static org.junit.Assert.assertArrayEquals;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import junit.framework.TestCase;
import java.io.ByteArrayInputStream;
diff --git a/errorprone/refaster/EfficientXml.java b/errorprone/refaster/EfficientXml.java
index ae797c4..87a902a 100644
--- a/errorprone/refaster/EfficientXml.java
+++ b/errorprone/refaster/EfficientXml.java
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.internal.util.XmlUtils;
diff --git a/errorprone/refaster/EfficientXml.java.refaster b/errorprone/refaster/EfficientXml.java.refaster
index 750c2db..c285e9b 100644
--- a/errorprone/refaster/EfficientXml.java.refaster
+++ b/errorprone/refaster/EfficientXml.java.refaster
Binary files differ
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 00be5a6e..77284c41 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -109,6 +109,12 @@
return (mSplitRule instanceof SplitPlaceholderRule);
}
+ @NonNull
+ SplitInfo toSplitInfo() {
+ return new SplitInfo(mPrimaryContainer.toActivityStack(),
+ mSecondaryContainer.toActivityStack(), mSplitAttributes);
+ }
+
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index bf7326a..1d513e4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1422,6 +1422,11 @@
@GuardedBy("mLock")
void updateContainer(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
+ if (!container.getTaskContainer().isVisible()) {
+ // Wait until the Task is visible to avoid unnecessary update when the Task is still in
+ // background.
+ return;
+ }
if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
@@ -1643,16 +1648,14 @@
/**
* Notifies listeners about changes to split states if necessary.
*/
+ @VisibleForTesting
@GuardedBy("mLock")
- private void updateCallbackIfNecessary() {
- if (mEmbeddingCallback == null) {
+ void updateCallbackIfNecessary() {
+ if (mEmbeddingCallback == null || !readyToReportToClient()) {
return;
}
- if (!allActivitiesCreated()) {
- return;
- }
- List<SplitInfo> currentSplitStates = getActiveSplitStates();
- if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) {
+ final List<SplitInfo> currentSplitStates = getActiveSplitStates();
+ if (mLastReportedSplitStates.equals(currentSplitStates)) {
return;
}
mLastReportedSplitStates.clear();
@@ -1661,48 +1664,27 @@
}
/**
- * @return a list of descriptors for currently active split states. If the value returned is
- * null, that indicates that the active split states are in an intermediate state and should
- * not be reported.
+ * Returns a list of descriptors for currently active split states.
*/
@GuardedBy("mLock")
- @Nullable
+ @NonNull
private List<SplitInfo> getActiveSplitStates() {
- List<SplitInfo> splitStates = new ArrayList<>();
+ final List<SplitInfo> splitStates = new ArrayList<>();
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<SplitContainer> splitContainers = mTaskContainers.valueAt(i)
- .mSplitContainers;
- for (SplitContainer container : splitContainers) {
- if (container.getPrimaryContainer().isEmpty()
- || container.getSecondaryContainer().isEmpty()) {
- // We are in an intermediate state because either the split container is about
- // to be removed or the primary or secondary container are about to receive an
- // activity.
- return null;
- }
- final ActivityStack primaryContainer = container.getPrimaryContainer()
- .toActivityStack();
- final ActivityStack secondaryContainer = container.getSecondaryContainer()
- .toActivityStack();
- final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
- container.getSplitAttributes());
- splitStates.add(splitState);
- }
+ mTaskContainers.valueAt(i).getSplitStates(splitStates);
}
return splitStates;
}
/**
- * Checks if all activities that are registered with the containers have already appeared in
- * the client.
+ * Whether we can now report the split states to the client.
*/
- private boolean allActivitiesCreated() {
+ @GuardedBy("mLock")
+ private boolean readyToReportToClient() {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
- for (TaskFragmentContainer container : containers) {
- if (!container.taskInfoActivityCountMatchesCreated()) {
- return false;
- }
+ if (mTaskContainers.valueAt(i).isInIntermediateState()) {
+ // If any Task is in an intermediate state, wait for the server update.
+ return false;
}
}
return true;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 00943f2d..231da05 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -221,6 +221,24 @@
return mContainers.indexOf(child);
}
+ /** Whether the Task is in an intermediate state waiting for the server update.*/
+ boolean isInIntermediateState() {
+ for (TaskFragmentContainer container : mContainers) {
+ if (container.isInIntermediateState()) {
+ // We are in an intermediate state to wait for server update on this TaskFragment.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
+ void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
+ for (SplitContainer container : mSplitContainers) {
+ outSplitStates.add(container.toSplitInfo());
+ }
+ }
+
/**
* A wrapper class which contains the display ID and {@link Configuration} of a
* {@link TaskContainer}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index ef5ea56..a7d47ef 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -161,7 +161,7 @@
// The position should be 0-based as we will post translate in
// TaskFragmentAnimationAdapter#onAnimationUpdate
final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
- 0, 0);
+ startBounds.top - endBounds.top, 0);
endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
endSet.addAnimation(endTranslate);
// The end leash is resizing, we should update the window crop based on the clip rect.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 18712ae..71b8840 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -166,16 +166,34 @@
return allActivities;
}
- /**
- * Checks if the count of activities from the same process in task fragment info corresponds to
- * the ones created and available on the client side.
- */
- boolean taskInfoActivityCountMatchesCreated() {
+ /** Whether the TaskFragment is in an intermediate state waiting for the server update.*/
+ boolean isInIntermediateState() {
if (mInfo == null) {
- return false;
+ // Haven't received onTaskFragmentAppeared event.
+ return true;
}
- return mPendingAppearedActivities.isEmpty()
- && mInfo.getActivities().size() == collectNonFinishingActivities().size();
+ if (mInfo.isEmpty()) {
+ // Empty TaskFragment will be removed or will have activity launched into it soon.
+ return true;
+ }
+ if (!mPendingAppearedActivities.isEmpty()) {
+ // Reparented activity hasn't appeared.
+ return true;
+ }
+ // Check if there is any reported activity that is no longer alive.
+ for (IBinder token : mInfo.getActivities()) {
+ final Activity activity = mController.getActivity(token);
+ if (activity == null && !mTaskContainer.isVisible()) {
+ // Activity can be null if the activity is not attached to process yet. That can
+ // happen when the activity is started in background.
+ continue;
+ }
+ if (activity == null || activity.isFinishing()) {
+ // One of the reported activity is no longer alive, wait for the server update.
+ return true;
+ }
+ }
+ return false;
}
@NonNull
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index a403031..87d0278 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -102,6 +102,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
/**
* Test class for {@link SplitController}.
@@ -132,6 +133,8 @@
private SplitController mSplitController;
private SplitPresenter mSplitPresenter;
+ private Consumer<List<SplitInfo>> mEmbeddingCallback;
+ private List<SplitInfo> mSplitInfos;
private TransactionManager mTransactionManager;
@Before
@@ -141,9 +144,16 @@
.getCurrentWindowLayoutInfo(anyInt(), any());
mSplitController = new SplitController(mWindowLayoutComponent);
mSplitPresenter = mSplitController.mPresenter;
+ mSplitInfos = new ArrayList<>();
+ mEmbeddingCallback = splitInfos -> {
+ mSplitInfos.clear();
+ mSplitInfos.addAll(splitInfos);
+ };
+ mSplitController.setSplitInfoCallback(mEmbeddingCallback);
mTransactionManager = mSplitController.mTransactionManager;
spyOn(mSplitController);
spyOn(mSplitPresenter);
+ spyOn(mEmbeddingCallback);
spyOn(mTransactionManager);
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
@@ -329,6 +339,30 @@
}
@Test
+ public void testUpdateContainer_skipIfTaskIsInvisible() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ final TaskFragmentContainer taskFragmentContainer = taskContainer.mContainers.get(0);
+ spyOn(taskContainer);
+
+ // No update when the Task is invisible.
+ clearInvocations(mSplitPresenter);
+ doReturn(false).when(taskContainer).isVisible();
+ mSplitController.updateContainer(mTransaction, taskFragmentContainer);
+
+ verify(mSplitPresenter, never()).updateSplitContainer(any(), any(), any());
+
+ // Update the split when the Task is visible.
+ doReturn(true).when(taskContainer).isVisible();
+ mSplitController.updateContainer(mTransaction, taskFragmentContainer);
+
+ verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0),
+ taskFragmentContainer, mTransaction);
+ }
+
+ @Test
public void testOnStartActivityResultError() {
final Intent intent = new Intent();
final TaskContainer taskContainer = createTestTaskContainer();
@@ -1162,14 +1196,69 @@
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
}
+ @Test
+ public void testSplitInfoCallback_reportSplit() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+
+ mSplitController.updateCallbackIfNecessary();
+ assertEquals(1, mSplitInfos.size());
+ final SplitInfo splitInfo = mSplitInfos.get(0);
+ assertEquals(1, splitInfo.getPrimaryActivityStack().getActivities().size());
+ assertEquals(1, splitInfo.getSecondaryActivityStack().getActivities().size());
+ assertEquals(r0, splitInfo.getPrimaryActivityStack().getActivities().get(0));
+ assertEquals(r1, splitInfo.getSecondaryActivityStack().getActivities().get(0));
+ }
+
+ @Test
+ public void testSplitInfoCallback_reportSplitInMultipleTasks() {
+ final int taskId0 = 1;
+ final int taskId1 = 2;
+ final Activity r0 = createMockActivity(taskId0);
+ final Activity r1 = createMockActivity(taskId0);
+ final Activity r2 = createMockActivity(taskId1);
+ final Activity r3 = createMockActivity(taskId1);
+ addSplitTaskFragments(r0, r1);
+ addSplitTaskFragments(r2, r3);
+
+ mSplitController.updateCallbackIfNecessary();
+ assertEquals(2, mSplitInfos.size());
+ }
+
+ @Test
+ public void testSplitInfoCallback_doNotReportIfInIntermediateState() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+ final TaskFragmentContainer tf0 = mSplitController.getContainerWithActivity(r0);
+ final TaskFragmentContainer tf1 = mSplitController.getContainerWithActivity(r1);
+ spyOn(tf0);
+ spyOn(tf1);
+
+ // Do not report if activity has not appeared in the TaskFragmentContainer in split.
+ doReturn(true).when(tf0).isInIntermediateState();
+ mSplitController.updateCallbackIfNecessary();
+ verify(mEmbeddingCallback, never()).accept(any());
+
+ doReturn(false).when(tf0).isInIntermediateState();
+ mSplitController.updateCallbackIfNecessary();
+ verify(mEmbeddingCallback).accept(any());
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
+ return createMockActivity(TASK_ID);
+ }
+
+ /** Creates a mock activity in the organizer process. */
+ private Activity createMockActivity(int taskId) {
final Activity activity = mock(Activity.class);
doReturn(mActivityResources).when(activity).getResources();
final IBinder activityToken = new Binder();
doReturn(activityToken).when(activity).getActivityToken();
doReturn(activity).when(mSplitController).getActivity(activityToken);
- doReturn(TASK_ID).when(activity).getTaskId();
+ doReturn(taskId).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
return activity;
@@ -1177,7 +1266,8 @@
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
- final TaskFragmentContainer container = mSplitController.newContainer(activity, TASK_ID);
+ final TaskFragmentContainer container = mSplitController.newContainer(activity,
+ activity.getTaskId());
setupTaskFragmentInfo(container, activity);
return container;
}
@@ -1268,7 +1358,7 @@
// We need to set those in case we are not respecting clear top.
// TODO(b/231845476) we should always respect clearTop.
- final int windowingMode = mSplitController.getTaskContainer(TASK_ID)
+ final int windowingMode = mSplitController.getTaskContainer(primaryContainer.getTaskId())
.getWindowingModeForSplitTaskFragment(TASK_BOUNDS);
primaryContainer.setLastRequestedWindowingMode(windowingMode);
secondaryContainer.setLastRequestedWindowingMode(windowingMode);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 35415d8..d43c471 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -334,6 +334,70 @@
assertFalse(container.hasActivity(mActivity.getActivityToken()));
}
+ @Test
+ public void testIsInIntermediateState() {
+ // True if no info set.
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController);
+ spyOn(taskContainer);
+ doReturn(true).when(taskContainer).isVisible();
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // True if empty info set.
+ final List<IBinder> activities = new ArrayList<>();
+ doReturn(activities).when(mInfo).getActivities();
+ doReturn(true).when(mInfo).isEmpty();
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // False if info is not empty.
+ doReturn(false).when(mInfo).isEmpty();
+ container.setInfo(mTransaction, mInfo);
+
+ assertFalse(container.isInIntermediateState());
+ assertFalse(taskContainer.isInIntermediateState());
+
+ // True if there is pending appeared activity.
+ container.addPendingAppearedActivity(mActivity);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // True if the activity is finishing.
+ activities.add(mActivity.getActivityToken());
+ doReturn(true).when(mActivity).isFinishing();
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // False if the activity is not finishing.
+ doReturn(false).when(mActivity).isFinishing();
+ container.setInfo(mTransaction, mInfo);
+
+ assertFalse(container.isInIntermediateState());
+ assertFalse(taskContainer.isInIntermediateState());
+
+ // True if there is a token that can't find associated activity.
+ activities.clear();
+ activities.add(new Binder());
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // False if there is a token that can't find associated activity when the Task is invisible.
+ doReturn(false).when(taskContainer).isVisible();
+
+ assertFalse(container.isInIntermediateState());
+ assertFalse(taskContainer.isInIntermediateState());
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 30c3d50..df5f921 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -23,6 +23,10 @@
TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
<bool name="config_registerShellTaskOrganizerOnInit">true</bool>
+ <!-- Determines whether to register the shell transitions on init.
+ TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
+ <bool name="config_registerShellTransitionsOnInit">true</bool>
+
<!-- Animation duration for PIP when entering. -->
<integer name="config_pipEnterAnimationDuration">425</integer>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 48c5f64..bd2ea9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -41,7 +41,6 @@
import android.window.WindowContainerTransaction;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -122,7 +121,7 @@
/** Until all users are converted, we may have mixed-use (eg. Car). */
private boolean isUsingShellTransitions() {
- return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS;
+ return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
index 83335ac..07d5012 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
@@ -87,6 +87,10 @@
// Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
}
+ boolean isEnabled() {
+ return mTransitions.isRegistered();
+ }
+
/**
* Looks through the pending transitions for one matching `taskView`.
* @param taskView the pending transition should be for this.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 490975c..921861a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -303,6 +303,7 @@
// 3. Animate the TaskFragment using Activity Change info (start/end bounds).
// This is because the TaskFragment surface/change won't contain the Activity's before its
// reparent.
+ Animation changeAnimation = null;
for (TransitionInfo.Change change : info.getChanges()) {
if (change.getMode() != TRANSIT_CHANGE
|| change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
@@ -325,8 +326,14 @@
}
}
+ // There are two animations in the array. The first one is for the start leash
+ // (snapshot), and the second one is for the end leash (TaskFragment).
final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
boundsAnimationChange.getEndAbsBounds());
+ // Keep track as we might need to add background color for the animation.
+ // Although there may be multiple change animation, record one of them is sufficient
+ // because the background color will be added to the root leash for the whole animation.
+ changeAnimation = animations[1];
// Create a screenshot based on change, but attach it to the top of the
// boundsAnimationChange.
@@ -345,6 +352,9 @@
animations[1], boundsAnimationChange));
}
+ // If there is no corresponding open/close window with the change, we should show background
+ // color to cover the empty part of the screen.
+ boolean shouldShouldBackgroundColor = true;
// Handle the other windows that don't have bounds change in the same transition.
for (TransitionInfo.Change change : info.getChanges()) {
if (handledChanges.contains(change)) {
@@ -359,11 +369,20 @@
animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
} else if (Transitions.isClosingType(change.getMode())) {
animation = mAnimationSpec.createChangeBoundsCloseAnimation(change);
+ shouldShouldBackgroundColor = false;
} else {
animation = mAnimationSpec.createChangeBoundsOpenAnimation(change);
+ shouldShouldBackgroundColor = false;
}
adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change));
}
+
+ if (shouldShouldBackgroundColor && changeAnimation != null) {
+ // Change animation may leave part of the screen empty. Show background color to cover
+ // that.
+ changeAnimation.setShowBackdrop(true);
+ }
+
return adapters;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 58b2366..2bb7369 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -158,7 +158,7 @@
// The position should be 0-based as we will post translate in
// ActivityEmbeddingAnimationAdapter#onAnimationUpdate
final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
- 0, 0);
+ startBounds.top - endBounds.top, 0);
endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
endSet.addAnimation(endTranslate);
// The end leash is resizing, we should update the window crop based on the clip rect.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index db5de43..dc5b9a1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -72,13 +72,9 @@
private static final String TAG = "BackAnimationController";
private static final int SETTING_VALUE_OFF = 0;
private static final int SETTING_VALUE_ON = 1;
- private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
- "persist.wm.debug.predictive_back_progress_threshold";
public static final boolean IS_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back",
- SETTING_VALUE_ON) != SETTING_VALUE_ON;
- private static final int PROGRESS_THRESHOLD = SystemProperties
- .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
+ SETTING_VALUE_ON) == SETTING_VALUE_ON;
/** Flag for U animation features */
public static boolean IS_U_ANIMATION_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 725b205..3972b59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -671,10 +671,18 @@
return;
}
+ mAddedToWindowManager = false;
+ // Put on background for this binder call, was causing jank
+ mBackgroundExecutor.execute(() -> {
+ try {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ } catch (IllegalArgumentException e) {
+ // Not sure if this happens in production, but was happening in tests
+ // (b/253647225)
+ e.printStackTrace();
+ }
+ });
try {
- mAddedToWindowManager = false;
- // Put on background for this binder call, was causing jank
- mBackgroundExecutor.execute(() -> mContext.unregisterReceiver(mBroadcastReceiver));
if (mStackView != null) {
mWindowManager.removeView(mStackView);
mBubbleData.getOverflow().cleanUpExpandedState();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 266cf29..0bb76b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -21,12 +21,10 @@
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.content.ComponentName;
-import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Slog;
import android.util.SparseArray;
import android.view.IDisplayWindowInsetsController;
@@ -34,16 +32,16 @@
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.view.IInputMethodManager;
+import com.android.internal.inputmethod.IInputMethodManagerGlobal;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
@@ -209,7 +207,7 @@
public class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener {
final int mDisplayId;
final InsetsState mInsetsState = new InsetsState();
- final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
InsetsSourceControl mImeSourceControl = null;
int mAnimationDirection = DIRECTION_NONE;
ValueAnimator mAnimation = null;
@@ -332,8 +330,7 @@
}
@Override
- public void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ public void topFocusedWindowChanged(ComponentName component, int requestedVisibleTypes) {
// Do nothing
}
@@ -342,10 +339,12 @@
*/
private void setVisibleDirectly(boolean visible) {
mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
- mRequestedVisibilities.setVisibility(InsetsState.ITYPE_IME, visible);
+ mRequestedVisibleTypes = visible
+ ? mRequestedVisibleTypes | WindowInsets.Type.ime()
+ : mRequestedVisibleTypes & ~WindowInsets.Type.ime();
try {
- mWmService.updateDisplayWindowRequestedVisibilities(mDisplayId,
- mRequestedVisibilities);
+ mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId,
+ mRequestedVisibleTypes);
} catch (RemoteException e) {
}
}
@@ -514,16 +513,10 @@
}
void removeImeSurface() {
- final IInputMethodManager imms = getImms();
- if (imms != null) {
- try {
- // Remove the IME surface to make the insets invisible for
- // non-client controlled insets.
- imms.removeImeSurface();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to remove IME surface.", e);
- }
- }
+ // Remove the IME surface to make the insets invisible for
+ // non-client controlled insets.
+ IInputMethodManagerGlobal.removeImeSurface(
+ e -> Slog.e(TAG, "Failed to remove IME surface.", e));
}
/**
@@ -597,11 +590,6 @@
}
}
- public IInputMethodManager getImms() {
- return IInputMethodManager.Stub.asInterface(
- ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
- }
-
private static boolean haveSameLeash(InsetsSourceControl a, InsetsSourceControl b) {
if (a == b) {
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 90a01f8..8d4a09d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -24,7 +24,7 @@
import android.view.IWindowManager;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
import androidx.annotation.BinderThread;
@@ -177,13 +177,13 @@
}
private void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ @InsetsType int requestedVisibleTypes) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
return;
}
for (OnInsetsChangedListener listener : listeners) {
- listener.topFocusedWindowChanged(component, requestedVisibilities);
+ listener.topFocusedWindowChanged(component, requestedVisibleTypes);
}
}
@@ -192,9 +192,9 @@
extends IDisplayWindowInsetsController.Stub {
@Override
public void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) throws RemoteException {
+ @InsetsType int requestedVisibleTypes) throws RemoteException {
mMainExecutor.execute(() -> {
- PerDisplay.this.topFocusedWindowChanged(component, requestedVisibilities);
+ PerDisplay.this.topFocusedWindowChanged(component, requestedVisibleTypes);
});
}
@@ -239,11 +239,13 @@
/**
* Called when top focused window changes to determine whether or not to take over insets
* control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
+ *
* @param component The application component that is open in the top focussed window.
- * @param requestedVisibilities The insets visibilities requested by the focussed window.
+ * @param requestedVisibleTypes The {@link InsetsType} requested visible by the focused
+ * window.
*/
default void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {}
+ @InsetsType int requestedVisibleTypes) {}
/**
* Called when the window insets configuration has changed.
@@ -259,17 +261,17 @@
/**
* Called when a set of insets source window should be shown by policy.
*
- * @param types internal insets types (WindowInsets.Type.InsetsType) to show
+ * @param types {@link InsetsType} to show
* @param fromIme true if this request originated from IME (InputMethodService).
*/
- default void showInsets(int types, boolean fromIme) {}
+ default void showInsets(@InsetsType int types, boolean fromIme) {}
/**
* Called when a set of insets source window should be hidden by policy.
*
- * @param types internal insets types (WindowInsets.Type.InsetsType) to hide
+ * @param types {@link InsetsType} to hide
* @param fromIme true if this request originated from IME (InputMethodService).
*/
- default void hideInsets(int types, boolean fromIme) {}
+ default void hideInsets(@InsetsType int types, boolean fromIme) {}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 64dbfbb..8b8e192 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -507,6 +507,10 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
+ if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
+ // TODO(b/238217847): Force override shell init if registration is disabled
+ shellInit = new ShellInit(mainExecutor);
+ }
return new Transitions(context, shellInit, shellController, organizer, pool,
displayController, mainExecutor, mainHandler, animExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index b9746e3..cbed4b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -115,8 +115,8 @@
// coordinates so offset the bounds to 0,0
mTmpDestinationRect.offsetTo(0, 0);
mTmpDestinationRect.inset(insets);
- // Scale by the shortest edge and offset such that the top/left of the scaled inset source
- // rect aligns with the top/left of the destination bounds
+ // Scale to the bounds no smaller than the destination and offset such that the top/left
+ // of the scaled inset source rect aligns with the top/left of the destination bounds
final float scale;
if (isInPipDirection
&& sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) {
@@ -129,9 +129,8 @@
: (float) destinationBounds.height() / sourceBounds.height();
scale = (1 - fraction) * startScale + fraction * endScale;
} else {
- scale = sourceBounds.width() <= sourceBounds.height()
- ? (float) destinationBounds.width() / sourceBounds.width()
- : (float) destinationBounds.height() / sourceBounds.height();
+ scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
+ (float) destinationBounds.height() / sourceBounds.height());
}
final float left = destinationBounds.left - insets.left * scale;
final float top = destinationBounds.top - insets.top * scale;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index d7ca791..21a1310 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -108,6 +108,14 @@
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
@NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
+ final TransitSession pendingTransition = getPendingTransition(transition);
+ if (pendingTransition != null && pendingTransition.mCanceled) {
+ // The pending transition was canceled, so skip playing animation.
+ t.apply();
+ onFinish(null /* wct */, null /* wctCB */);
+ return;
+ }
+
// Play some place-holder fade animations
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -170,9 +178,7 @@
}
boolean isPendingTransition(IBinder transition) {
- return isPendingEnter(transition)
- || isPendingDismiss(transition)
- || isPendingRecent(transition);
+ return getPendingTransition(transition) != null;
}
boolean isPendingEnter(IBinder transition) {
@@ -187,22 +193,38 @@
return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
}
+ @Nullable
+ private TransitSession getPendingTransition(IBinder transition) {
+ if (isPendingEnter(transition)) {
+ return mPendingEnter;
+ } else if (isPendingRecent(transition)) {
+ return mPendingRecent;
+ } else if (isPendingDismiss(transition)) {
+ return mPendingDismiss;
+ }
+
+ return null;
+ }
+
/** Starts a transition to enter split with a remote transition animator. */
IBinder startEnterTransition(
@WindowManager.TransitionType int transitType,
WindowContainerTransaction wct,
@Nullable RemoteTransition remoteTransition,
Transitions.TransitionHandler handler,
- @Nullable TransitionCallback callback) {
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- setEnterTransition(transition, remoteTransition, callback);
+ setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback);
return transition;
}
/** Sets a transition to enter split. */
void setEnterTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
- mPendingEnter = new TransitSession(transition, callback);
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
+ mPendingEnter = new TransitSession(transition, consumedCallback, finishedCallback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -237,8 +259,9 @@
}
void setRecentTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
- mPendingRecent = new TransitSession(transition, callback);
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionFinishedCallback finishCallback) {
+ mPendingRecent = new TransitSession(transition, null /* consumedCb */, finishCallback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -248,7 +271,7 @@
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
- + " deduced Enter recent panel");
+ + " deduced Enter recent panel");
}
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
@@ -256,14 +279,9 @@
if (mergeTarget != mAnimatingTransition) return;
if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
- mPendingRecent.mCallback = new TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // Since there's an entering transition merged, recent transition no longer
- // need to handle entering split screen after the transition finished.
- }
- };
+ // Since there's an entering transition merged, recent transition no longer
+ // need to handle entering split screen after the transition finished.
+ mPendingRecent.setFinishedCallback(null);
}
if (mActiveRemoteHandler != null) {
@@ -277,7 +295,7 @@
}
boolean end() {
- // If its remote, there's nothing we can do right now.
+ // If It's remote, there's nothing we can do right now.
if (mActiveRemoteHandler != null) return false;
for (int i = mAnimations.size() - 1; i >= 0; --i) {
final Animator anim = mAnimations.get(i);
@@ -290,20 +308,20 @@
@Nullable SurfaceControl.Transaction finishT) {
if (isPendingEnter(transition)) {
if (!aborted) {
- // An enter transition got merged, appends the rest operations to finish entering
+ // An entering transition got merged, appends the rest operations to finish entering
// split screen.
mStageCoordinator.finishEnterSplitScreen(finishT);
mPendingRemoteHandler = null;
}
- mPendingEnter.mCallback.onTransitionConsumed(aborted);
+ mPendingEnter.onConsumed(aborted);
mPendingEnter = null;
mPendingRemoteHandler = null;
} else if (isPendingDismiss(transition)) {
- mPendingDismiss.mCallback.onTransitionConsumed(aborted);
+ mPendingDismiss.onConsumed(aborted);
mPendingDismiss = null;
} else if (isPendingRecent(transition)) {
- mPendingRecent.mCallback.onTransitionConsumed(aborted);
+ mPendingRecent.onConsumed(aborted);
mPendingRecent = null;
mPendingRemoteHandler = null;
}
@@ -312,23 +330,16 @@
void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
if (!mAnimations.isEmpty()) return;
- TransitionCallback callback = null;
+ if (wct == null) wct = new WindowContainerTransaction();
if (isPendingEnter(mAnimatingTransition)) {
- callback = mPendingEnter.mCallback;
+ mPendingEnter.onFinished(wct, mFinishTransaction);
mPendingEnter = null;
- }
- if (isPendingDismiss(mAnimatingTransition)) {
- callback = mPendingDismiss.mCallback;
- mPendingDismiss = null;
- }
- if (isPendingRecent(mAnimatingTransition)) {
- callback = mPendingRecent.mCallback;
+ } else if (isPendingRecent(mAnimatingTransition)) {
+ mPendingRecent.onFinished(wct, mFinishTransaction);
mPendingRecent = null;
- }
-
- if (callback != null) {
- if (wct == null) wct = new WindowContainerTransaction();
- callback.onTransitionFinished(wct, mFinishTransaction);
+ } else if (isPendingDismiss(mAnimatingTransition)) {
+ mPendingDismiss.onFinished(wct, mFinishTransaction);
+ mPendingDismiss = null;
}
mPendingRemoteHandler = null;
@@ -363,10 +374,7 @@
onFinish(null /* wct */, null /* wctCB */);
});
};
- va.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) { }
-
+ va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finisher.run();
@@ -376,9 +384,6 @@
public void onAnimationCancel(Animator animation) {
finisher.run();
}
-
- @Override
- public void onAnimationRepeat(Animator animation) { }
});
mAnimations.add(va);
mTransitions.getAnimExecutor().execute(va::start);
@@ -432,24 +437,66 @@
|| info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
}
- /** Clean-up callbacks for transition. */
- interface TransitionCallback {
- /** Calls when the transition got consumed. */
- default void onTransitionConsumed(boolean aborted) {}
+ /** Calls when the transition got consumed. */
+ interface TransitionConsumedCallback {
+ void onConsumed(boolean aborted);
+ }
- /** Calls when the transition finished. */
- default void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {}
+ /** Calls when the transition finished. */
+ interface TransitionFinishedCallback {
+ void onFinished(WindowContainerTransaction wct, SurfaceControl.Transaction t);
}
/** Session for a transition and its clean-up callback. */
static class TransitSession {
final IBinder mTransition;
- TransitionCallback mCallback;
+ TransitionConsumedCallback mConsumedCallback;
+ TransitionFinishedCallback mFinishedCallback;
- TransitSession(IBinder transition, @Nullable TransitionCallback callback) {
+ /** Whether the transition was canceled. */
+ boolean mCanceled;
+
+ TransitSession(IBinder transition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
mTransition = transition;
- mCallback = callback != null ? callback : new TransitionCallback() {};
+ mConsumedCallback = consumedCallback;
+ mFinishedCallback = finishedCallback;
+
+ }
+
+ /** Sets transition consumed callback. */
+ void setConsumedCallback(@Nullable TransitionConsumedCallback callback) {
+ mConsumedCallback = callback;
+ }
+
+ /** Sets transition finished callback. */
+ void setFinishedCallback(@Nullable TransitionFinishedCallback callback) {
+ mFinishedCallback = callback;
+ }
+
+ /**
+ * Cancels the transition. This should be called before playing animation. A canceled
+ * transition will skip playing animation.
+ *
+ * @param finishedCb new finish callback to override.
+ */
+ void cancel(@Nullable TransitionFinishedCallback finishedCb) {
+ mCanceled = true;
+ setFinishedCallback(finishedCb);
+ }
+
+ void onConsumed(boolean aborted) {
+ if (mConsumedCallback != null) {
+ mConsumedCallback.onConsumed(aborted);
+ }
+ }
+
+ void onFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ if (mFinishedCallback != null) {
+ mFinishedCallback.onFinished(finishWct, finishT);
+ }
}
}
@@ -459,7 +506,7 @@
final @SplitScreen.StageType int mDismissTop;
DismissTransition(IBinder transition, int reason, int dismissTop) {
- super(transition, null /* callback */);
+ super(transition, null /* consumedCallback */, null /* finishedCallback */);
this.mReason = reason;
this.mDismissTop = dismissTop;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e2ac01f..943419b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -226,33 +226,36 @@
}
};
- private final SplitScreenTransitions.TransitionCallback mRecentTransitionCallback =
- new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // Check if the recent transition is finished by returning to the current split, so we
- // can restore the divider bar.
- for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
- final WindowContainerTransaction.HierarchyOp op =
- finishWct.getHierarchyOps().get(i);
- final IBinder container = op.getContainer();
- if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
- && (mMainStage.containsContainer(container)
- || mSideStage.containsContainer(container))) {
- updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */);
- setDividerVisibility(true, finishT);
- return;
- }
- }
+ private final SplitScreenTransitions.TransitionFinishedCallback
+ mRecentTransitionFinishedCallback =
+ new SplitScreenTransitions.TransitionFinishedCallback() {
+ @Override
+ public void onFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ // Check if the recent transition is finished by returning to the current
+ // split, so we
+ // can restore the divider bar.
+ for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
+ final WindowContainerTransaction.HierarchyOp op =
+ finishWct.getHierarchyOps().get(i);
+ final IBinder container = op.getContainer();
+ if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+ && (mMainStage.containsContainer(container)
+ || mSideStage.containsContainer(container))) {
+ updateSurfaceBounds(mSplitLayout, finishT,
+ false /* applyResizingOffset */);
+ setDividerVisibility(true, finishT);
+ return;
+ }
+ }
- // Dismiss the split screen if it's not returning to split.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
- setSplitsVisible(false);
- setDividerVisibility(false, finishT);
- logExit(EXIT_REASON_UNKNOWN);
- }
- };
+ // Dismiss the split screen if it's not returning to split.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+ setSplitsVisible(false);
+ setDividerVisibility(false, finishT);
+ logExit(EXIT_REASON_UNKNOWN);
+ }
+ };
protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
@@ -389,15 +392,11 @@
if (ENABLE_SHELL_TRANSITIONS) {
prepareEnterSplitScreen(wct);
mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct,
- null, this, new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
+ null, this, null /* consumedCallback */, (finishWct, finishT) -> {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
}
- });
+ } /* finishedCallback */);
} else {
if (!evictWct.isEmpty()) {
wct.merge(evictWct, true /* transfer */);
@@ -434,28 +433,25 @@
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
wct.sendPendingIntent(intent, fillInIntent, options);
+
+ // If split screen is not activated, we're expecting to open a pair of apps to split.
+ final int transitType = mMainStage.isActive()
+ ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
prepareEnterSplitScreen(wct, null /* taskInfo */, position);
- mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this,
- new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionConsumed(boolean aborted) {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if (aborted
- && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePositionAnimated(
- SplitLayout.reversePosition(mSideStagePosition));
- }
+ mSplitTransitions.startEnterTransition(transitType, wct, null, this,
+ aborted -> {
+ // Switch the split position if launching as MULTIPLE_TASK failed.
+ if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
+ setSideStagePositionAnimated(
+ SplitLayout.reversePosition(mSideStagePosition));
}
-
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
+ } /* consumedCallback */,
+ (finishWct, finishT) -> {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
}
- });
+ } /* finishedCallback */);
}
/** Launches an activity into split by legacy transition. */
@@ -564,9 +560,9 @@
/**
* Starts with the second task to a split pair in one transition.
*
- * @param wct transaction to start the first task
+ * @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
- * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
+ * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
@Nullable Bundle mainOptions, float splitRatio,
@@ -592,7 +588,7 @@
wct.startTask(mainTaskId, mainOptions);
mSplitTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null);
setEnterInstanceId(instanceId);
}
@@ -639,7 +635,7 @@
}
/**
- * @param wct transaction to start the first task
+ * @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
@@ -1082,15 +1078,15 @@
switch (exitReason) {
// One of the apps doesn't support MW
case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
- // User has explicitly dragged the divider to dismiss split
+ // User has explicitly dragged the divider to dismiss split
case EXIT_REASON_DRAG_DIVIDER:
- // Either of the split apps have finished
+ // Either of the split apps have finished
case EXIT_REASON_APP_FINISHED:
- // One of the children enters PiP
+ // One of the children enters PiP
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
- // One of the apps occludes lock screen.
+ // One of the apps occludes lock screen.
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
- // User has unlocked the device after folded
+ // User has unlocked the device after folded
case EXIT_REASON_DEVICE_FOLDED:
return true;
default:
@@ -1839,7 +1835,7 @@
|| activityType == ACTIVITY_TYPE_RECENTS) {
// Enter overview panel, so start recent transition.
mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
- mRecentTransitionCallback);
+ mRecentTransitionFinishedCallback);
} else if (mSplitTransitions.mPendingRecent == null) {
// If split-task is not controlled by recents animation
// and occluded by the other fullscreen task, dismiss both.
@@ -1853,8 +1849,8 @@
// One task is appearing into split, prepare to enter split screen.
out = new WindowContainerTransaction();
prepareEnterSplitScreen(out);
- mSplitTransitions.setEnterTransition(
- transition, request.getRemoteTransition(), null /* callback */);
+ mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
+ null /* consumedCallback */, null /* finishedCallback */);
}
}
return out;
@@ -1873,7 +1869,7 @@
}
final @WindowManager.TransitionType int type = request.getType();
if (isSplitActive() && !isOpeningType(type)
- && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
+ && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " One of the splits became "
+ "empty during a mixed transition (one not handled by split),"
+ " so make sure split-screen state is cleaned-up. "
@@ -2031,17 +2027,21 @@
}
}
- // TODO(b/250853925): fallback logic. Probably start a new transition to exit split before
- // applying anything here. Ideally consolidate with transition-merging.
if (info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
if (mainChild == null && sideChild == null) {
- throw new IllegalStateException("Launched a task in split, but didn't receive any"
- + " task in transition.");
+ Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
+ mSplitTransitions.mPendingEnter.cancel(null /* finishedCb */);
+ return true;
}
} else {
if (mainChild == null || sideChild == null) {
- throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ Log.w(TAG, "Launched 2 tasks in split, but didn't receive"
+ " 2 tasks in transition. Possibly one of them failed to launch");
+ final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
+ (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
+ mSplitTransitions.mPendingEnter.cancel(
+ (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+ return true;
}
}
@@ -2305,7 +2305,7 @@
final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(stageType, wct);
- mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType,
+ mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
mSplitUnsupportedToast.show();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 7b498e4..3929e83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -77,6 +77,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.ClientWindowFrames;
@@ -223,7 +224,7 @@
final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance,
windowFlags, windowPrivateFlags, taskBounds, orientation, activityType,
- topWindowInsetsState, clearWindowHandler, splashScreenExecutor);
+ info.requestedVisibleTypes, clearWindowHandler, splashScreenExecutor);
final Window window = snapshotSurface.mWindow;
final InsetsState tmpInsetsState = new InsetsState();
@@ -233,7 +234,7 @@
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay");
final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
- info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls,
+ info.requestedVisibleTypes, tmpInputChannel, tmpInsetsState, tmpControls,
new Rect(), sizeCompatScale);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (res < 0) {
@@ -263,7 +264,7 @@
public TaskSnapshotWindow(SurfaceControl surfaceControl,
TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds,
- int currentOrientation, int activityType, InsetsState topWindowInsetsState,
+ int currentOrientation, int activityType, @InsetsType int requestedVisibleTypes,
Runnable clearWindowHandler, ShellExecutor splashScreenExecutor) {
mSplashScreenExecutor = splashScreenExecutor;
mSession = WindowManagerGlobal.getWindowSession();
@@ -276,7 +277,7 @@
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
mTaskBounds = taskBounds;
mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
- windowPrivateFlags, appearance, taskDescription, 1f, topWindowInsetsState);
+ windowPrivateFlags, appearance, taskDescription, 1f, requestedVisibleTypes);
mStatusBarColor = taskDescription.getStatusBarColor();
mOrientationOnCreation = currentOrientation;
mActivityType = activityType;
@@ -569,11 +570,12 @@
private final int mWindowFlags;
private final int mWindowPrivateFlags;
private final float mScale;
- private final InsetsState mInsetsState;
+ private final @InsetsType int mRequestedVisibleTypes;
private final Rect mSystemBarInsets = new Rect();
SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
- TaskDescription taskDescription, float scale, InsetsState insetsState) {
+ TaskDescription taskDescription, float scale,
+ @InsetsType int requestedVisibleTypes) {
mWindowFlags = windowFlags;
mWindowPrivateFlags = windowPrivateFlags;
mScale = scale;
@@ -592,7 +594,7 @@
&& context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
mStatusBarPaint.setColor(mStatusBarColor);
mNavigationBarPaint.setColor(mNavigationBarColor);
- mInsetsState = insetsState;
+ mRequestedVisibleTypes = requestedVisibleTypes;
}
void setInsets(Rect systemBarInsets) {
@@ -603,7 +605,7 @@
final boolean forceBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) {
+ mRequestedVisibleTypes, mStatusBarColor, mWindowFlags, forceBarBackground)) {
return (int) (mSystemBarInsets.top * mScale);
} else {
return 0;
@@ -614,7 +616,7 @@
final boolean forceBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground);
+ mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags, forceBarBackground);
}
void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 9c2c2fa..af79386 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -619,12 +619,13 @@
// Animation length is already expected to be scaled.
va.overrideDurationScale(1.0f);
va.setDuration(anim.computeDurationHint());
- va.addUpdateListener(animation -> {
+ final ValueAnimator.AnimatorUpdateListener updateListener = animation -> {
final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
position, cornerRadius, clipRect);
- });
+ };
+ va.addUpdateListener(updateListener);
final Runnable finisher = () -> {
applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
@@ -637,20 +638,30 @@
});
};
va.addListener(new AnimatorListenerAdapter() {
+ // It is possible for the end/cancel to be called more than once, which may cause
+ // issues if the animating surface has already been released. Track the finished
+ // state here to skip duplicate callbacks. See b/252872225.
private boolean mFinished = false;
@Override
public void onAnimationEnd(Animator animation) {
- if (mFinished) return;
- mFinished = true;
- finisher.run();
+ onFinish();
}
@Override
public void onAnimationCancel(Animator animation) {
+ onFinish();
+ }
+
+ private void onFinish() {
if (mFinished) return;
mFinished = true;
finisher.run();
+ // The update listener can continue to be called after the animation has ended if
+ // end() is called manually again before the finisher removes the animation.
+ // Remove it manually here to prevent animating a released surface.
+ // See b/252872225.
+ va.removeUpdateListener(updateListener);
}
});
animations.add(va);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 394d6f6..63d31cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -122,6 +122,8 @@
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
+ private boolean mIsRegistered = false;
+
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
@@ -163,19 +165,18 @@
displayController, pool, mainExecutor, mainHandler, animExecutor);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
mShellController = shellController;
- shellInit.addInitCallback(this::onInit, this);
- }
-
- private void onInit() {
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
- this::createExternalInterface, this);
-
// The very last handler (0 in the list) should be the default one.
mHandlers.add(mDefaultTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
// Next lowest priority is remote transitions.
mHandlers.add(mRemoteTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+ this::createExternalInterface, this);
ContentResolver resolver = mContext.getContentResolver();
mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
@@ -186,13 +187,23 @@
new SettingsObserver());
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mIsRegistered = true;
// Register this transition handler with Core
- mOrganizer.registerTransitionPlayer(mPlayerImpl);
+ try {
+ mOrganizer.registerTransitionPlayer(mPlayerImpl);
+ } catch (RuntimeException e) {
+ mIsRegistered = false;
+ throw e;
+ }
// Pre-load the instance.
TransitionMetrics.getInstance();
}
}
+ public boolean isRegistered() {
+ return mIsRegistered;
+ }
+
private float getTransitionAnimationScaleSetting() {
return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 9967e5f..a6f19e7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -39,7 +39,6 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.view.IInputMethodManager;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.sysui.ShellInit;
@@ -56,8 +55,6 @@
@Mock
private SurfaceControl.Transaction mT;
@Mock
- private IInputMethodManager mMock;
- @Mock
private ShellInit mShellInit;
private DisplayImeController.PerDisplay mPerDisplay;
private Executor mExecutor;
@@ -77,10 +74,6 @@
}
}, mExecutor) {
@Override
- public IInputMethodManager getImms() {
- return mMock;
- }
- @Override
void removeImeSurface() { }
}.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 5f5a3c5..39db328 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -32,7 +32,7 @@
import android.view.IWindowManager;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
import androidx.test.filters.SmallTest;
@@ -108,7 +108,7 @@
mController.addInsetsChangedListener(SECOND_DISPLAY, secondListener);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).topFocusedWindowChanged(null,
- new InsetsVisibilities());
+ WindowInsets.Type.defaultVisible());
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false);
@@ -128,7 +128,7 @@
assertTrue(secondListener.hideInsetsCount == 0);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).topFocusedWindowChanged(null,
- new InsetsVisibilities());
+ WindowInsets.Type.defaultVisible());
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false);
@@ -175,8 +175,7 @@
int hideInsetsCount = 0;
@Override
- public void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ public void topFocusedWindowChanged(ComponentName component, int requestedVisibleTypes) {
topFocusedWindowChangedCount++;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index ea0033b..652f9b3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -181,7 +181,7 @@
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(testRemote), mStageCoordinator, null);
+ new RemoteTransition(testRemote), mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -421,7 +421,7 @@
TransitionInfo enterInfo = createEnterPairInfo();
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null);
+ new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index e5ae296..11fda8b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -249,7 +249,7 @@
doReturn(WindowManagerGlobal.ADD_OKAY).when(session).addToDisplay(
any() /* window */, any() /* attrs */,
anyInt() /* viewVisibility */, anyInt() /* displayId */,
- any() /* requestedVisibility */, any() /* outInputChannel */,
+ anyInt() /* requestedVisibleTypes */, any() /* outInputChannel */,
any() /* outInsetsState */, any() /* outActiveControls */,
any() /* outAttachedFrame */, any() /* outSizeCompatScale */);
TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
index 3de50bb..004df2a2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
@@ -39,9 +39,9 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
-import android.view.InsetsState;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.WindowInsets;
import android.window.TaskSnapshot;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -84,7 +84,8 @@
createTaskDescription(Color.WHITE, Color.RED, Color.BLUE),
0 /* appearance */, windowFlags /* windowFlags */, 0 /* privateWindowFlags */,
taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD,
- new InsetsState(), null /* clearWindow */, new TestShellExecutor());
+ WindowInsets.Type.defaultVisible(), null /* clearWindow */,
+ new TestShellExecutor());
}
private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 46fbe7c..5e8a623 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1020,40 +1020,6 @@
return base::unexpected(std::nullopt);
}
-template <typename TChar, typename SP>
-base::expected<size_t, NullOrIOError> ResStringPool::stringIndex(
- SP sp, std::unordered_map<SP, size_t>& map) const
-{
- AutoMutex lock(mStringIndexLock);
-
- if (map.empty()) {
- // build string index on the first call
- for (size_t i = 0; i < mHeader->stringCount; i++) {
- base::expected<SP, NullOrIOError> s;
- if constexpr(std::is_same_v<TChar, char16_t>) {
- s = stringAt(i);
- } else {
- s = string8At(i);
- }
- if (s.has_value()) {
- const auto r = map.insert({*s, i});
- if (!r.second) {
- ALOGE("failed to build string index, string id=%zu\n", i);
- }
- } else {
- return base::unexpected(s.error());
- }
- }
- }
-
- if (!map.empty()) {
- const auto result = map.find(sp);
- if (result != map.end())
- return result->second;
- }
- return base::unexpected(std::nullopt);
-}
-
base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_t* str,
size_t strLen) const
{
@@ -1061,28 +1027,134 @@
return base::unexpected(std::nullopt);
}
- if (kDebugStringPoolNoisy) {
- ALOGI("indexOfString (%s): %s", isUTF8() ? "UTF-8" : "UTF-16",
- String8(str, strLen).string());
- }
-
- base::expected<size_t, NullOrIOError> idx;
- if (isUTF8()) {
- auto str8 = String8(str, strLen);
- idx = stringIndex<char>(StringPiece(str8.c_str(), str8.size()), mStringIndex8);
- } else {
- idx = stringIndex<char16_t>(StringPiece16(str, strLen), mStringIndex16);
- }
-
- if (UNLIKELY(!idx.has_value())) {
- return base::unexpected(idx.error());
- }
-
- if (*idx < mHeader->stringCount) {
+ if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0) {
if (kDebugStringPoolNoisy) {
- ALOGI("MATCH! (idx=%zu)", *idx);
+ ALOGI("indexOfString UTF-8: %s", String8(str, strLen).string());
}
- return *idx;
+
+ // The string pool contains UTF 8 strings; we don't want to cause
+ // temporary UTF-16 strings to be created as we search.
+ if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
+ // Do a binary search for the string... this is a little tricky,
+ // because the strings are sorted with strzcmp16(). So to match
+ // the ordering, we need to convert strings in the pool to UTF-16.
+ // But we don't want to hit the cache, so instead we will have a
+ // local temporary allocation for the conversions.
+ size_t convBufferLen = strLen + 4;
+ std::vector<char16_t> convBuffer(convBufferLen);
+ ssize_t l = 0;
+ ssize_t h = mHeader->stringCount-1;
+
+ ssize_t mid;
+ while (l <= h) {
+ mid = l + (h - l)/2;
+ int c = -1;
+ const base::expected<StringPiece, NullOrIOError> s = string8At(mid);
+ if (UNLIKELY(IsIOError(s))) {
+ return base::unexpected(s.error());
+ }
+ if (s.has_value()) {
+ char16_t* end = utf8_to_utf16(reinterpret_cast<const uint8_t*>(s->data()),
+ s->size(), convBuffer.data(), convBufferLen);
+ c = strzcmp16(convBuffer.data(), end-convBuffer.data(), str, strLen);
+ }
+ if (kDebugStringPoolNoisy) {
+ ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
+ s->data(), c, (int)l, (int)mid, (int)h);
+ }
+ if (c == 0) {
+ if (kDebugStringPoolNoisy) {
+ ALOGI("MATCH!");
+ }
+ return mid;
+ } else if (c < 0) {
+ l = mid + 1;
+ } else {
+ h = mid - 1;
+ }
+ }
+ } else {
+ // It is unusual to get the ID from an unsorted string block...
+ // most often this happens because we want to get IDs for style
+ // span tags; since those always appear at the end of the string
+ // block, start searching at the back.
+ String8 str8(str, strLen);
+ const size_t str8Len = str8.size();
+ for (int i=mHeader->stringCount-1; i>=0; i--) {
+ const base::expected<StringPiece, NullOrIOError> s = string8At(i);
+ if (UNLIKELY(IsIOError(s))) {
+ return base::unexpected(s.error());
+ }
+ if (s.has_value()) {
+ if (kDebugStringPoolNoisy) {
+ ALOGI("Looking at %s, i=%d\n", s->data(), i);
+ }
+ if (str8Len == s->size()
+ && memcmp(s->data(), str8.string(), str8Len) == 0) {
+ if (kDebugStringPoolNoisy) {
+ ALOGI("MATCH!");
+ }
+ return i;
+ }
+ }
+ }
+ }
+
+ } else {
+ if (kDebugStringPoolNoisy) {
+ ALOGI("indexOfString UTF-16: %s", String8(str, strLen).string());
+ }
+
+ if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
+ // Do a binary search for the string...
+ ssize_t l = 0;
+ ssize_t h = mHeader->stringCount-1;
+
+ ssize_t mid;
+ while (l <= h) {
+ mid = l + (h - l)/2;
+ const base::expected<StringPiece16, NullOrIOError> s = stringAt(mid);
+ if (UNLIKELY(IsIOError(s))) {
+ return base::unexpected(s.error());
+ }
+ int c = s.has_value() ? strzcmp16(s->data(), s->size(), str, strLen) : -1;
+ if (kDebugStringPoolNoisy) {
+ ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
+ String8(s->data(), s->size()).string(), c, (int)l, (int)mid, (int)h);
+ }
+ if (c == 0) {
+ if (kDebugStringPoolNoisy) {
+ ALOGI("MATCH!");
+ }
+ return mid;
+ } else if (c < 0) {
+ l = mid + 1;
+ } else {
+ h = mid - 1;
+ }
+ }
+ } else {
+ // It is unusual to get the ID from an unsorted string block...
+ // most often this happens because we want to get IDs for style
+ // span tags; since those always appear at the end of the string
+ // block, start searching at the back.
+ for (int i=mHeader->stringCount-1; i>=0; i--) {
+ const base::expected<StringPiece16, NullOrIOError> s = stringAt(i);
+ if (UNLIKELY(IsIOError(s))) {
+ return base::unexpected(s.error());
+ }
+ if (kDebugStringPoolNoisy) {
+ ALOGI("Looking at %s, i=%d\n", String8(s->data(), s->size()).string(), i);
+ }
+ if (s.has_value() && strLen == s->size() &&
+ strzcmp16(s->data(), s->size(), str, strLen) == 0) {
+ if (kDebugStringPoolNoisy) {
+ ALOGI("MATCH!");
+ }
+ return i;
+ }
+ }
+ }
}
return base::unexpected(std::nullopt);
}
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 24628cd..9309091 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -41,7 +41,6 @@
#include <array>
#include <map>
#include <memory>
-#include <unordered_map>
namespace android {
@@ -563,17 +562,8 @@
incfs::map_ptr<uint32_t> mStyles;
uint32_t mStylePoolSize; // number of uint32_t
- // mStringIndex is used to quickly map a string to its ID
- mutable Mutex mStringIndexLock;
- mutable std::unordered_map<StringPiece, size_t> mStringIndex8;
- mutable std::unordered_map<StringPiece16, size_t> mStringIndex16;
-
base::expected<StringPiece, NullOrIOError> stringDecodeAt(
size_t idx, incfs::map_ptr<uint8_t> str, size_t encLen) const;
-
- template <typename TChar, typename SP=BasicStringPiece<TChar>>
- base::expected<size_t, NullOrIOError> stringIndex(
- SP str, std::unordered_map<SP, size_t>& map) const;
};
/**
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index d0d24a8..673041a 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -15,19 +15,20 @@
*/
#include "CanvasTransform.h"
-#include "Properties.h"
-#include "utils/Color.h"
+#include <SkAndroidFrameworkUtils.h>
#include <SkColorFilter.h>
#include <SkGradientShader.h>
+#include <SkHighContrastFilter.h>
#include <SkPaint.h>
#include <SkShader.h>
+#include <log/log.h>
#include <algorithm>
#include <cmath>
-#include <log/log.h>
-#include <SkHighContrastFilter.h>
+#include "Properties.h"
+#include "utils/Color.h"
namespace android::uirenderer {
@@ -82,27 +83,21 @@
paint.setColor(newColor);
if (paint.getShader()) {
- SkShader::GradientInfo info;
+ SkAndroidFrameworkUtils::LinearGradientInfo info;
std::array<SkColor, 10> _colorStorage;
std::array<SkScalar, _colorStorage.size()> _offsetStorage;
info.fColorCount = _colorStorage.size();
info.fColors = _colorStorage.data();
info.fColorOffsets = _offsetStorage.data();
- SkShader::GradientType type = paint.getShader()->asAGradient(&info);
- if (info.fColorCount <= 10) {
- switch (type) {
- case SkShader::kLinear_GradientType:
- for (int i = 0; i < info.fColorCount; i++) {
- info.fColors[i] = transformColor(transform, info.fColors[i]);
- }
- paint.setShader(SkGradientShader::MakeLinear(info.fPoint, info.fColors,
- info.fColorOffsets, info.fColorCount,
- info.fTileMode, info.fGradientFlags, nullptr));
- break;
- default:break;
+ if (SkAndroidFrameworkUtils::ShaderAsALinearGradient(paint.getShader(), &info) &&
+ info.fColorCount <= _colorStorage.size()) {
+ for (int i = 0; i < info.fColorCount; i++) {
+ info.fColors[i] = transformColor(transform, info.fColors[i]);
}
-
+ paint.setShader(SkGradientShader::MakeLinear(
+ info.fPoints, info.fColors, info.fColorOffsets, info.fColorCount,
+ info.fTileMode, info.fGradientFlags, nullptr));
}
}
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index 7a412a0..b38f9ea 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -207,7 +207,7 @@
/**
* Returns {@code true} if GNSS chipset supports single shot locating, {@code false} otherwise.
*/
- public boolean hasSingleShot() {
+ public boolean hasSingleShotFix() {
return (mTopFlags & TOP_HAL_CAPABILITY_SINGLE_SHOT) != 0;
}
@@ -482,7 +482,7 @@
if (hasMsa()) {
builder.append("MSA ");
}
- if (hasSingleShot()) {
+ if (hasSingleShotFix()) {
builder.append("SINGLE_SHOT ");
}
if (hasOnDemandTime()) {
@@ -602,7 +602,7 @@
/**
* Sets single shot locating capability.
*/
- public @NonNull Builder setHasSingleShot(boolean capable) {
+ public @NonNull Builder setHasSingleShotFix(boolean capable) {
mTopFlags = setFlag(mTopFlags, TOP_HAL_CAPABILITY_SINGLE_SHOT, capable);
return this;
}
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 23390fc..09f40e8 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -199,15 +199,15 @@
* <li>93-106 as the frequency channel number (FCN) (-7 to +6) plus 100.
* i.e. encode FCN of -7 as 93, 0 as 100, and +6 as 106</li>
* </ul></li>
- * <li>QZSS: 193-200</li>
+ * <li>QZSS: 183-206</li>
* <li>Galileo: 1-36</li>
- * <li>Beidou: 1-37</li>
+ * <li>Beidou: 1-63</li>
* <li>IRNSS: 1-14</li>
* </ul>
*
* @param satelliteIndex An index from zero to {@link #getSatelliteCount()} - 1
*/
- @IntRange(from = 1, to = 200)
+ @IntRange(from = 1, to = 206)
public int getSvid(@IntRange(from = 0) int satelliteIndex) {
return mSvidWithFlags[satelliteIndex] >> SVID_SHIFT_WIDTH;
}
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 428f2dc..2000d96 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -49,7 +49,6 @@
<style name="DescriptionSummary">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
- <item name="android:gravity">center</item>
<item name="android:layout_marginTop">18dp</item>
<item name="android:layout_marginLeft">18dp</item>
<item name="android:layout_marginRight">18dp</item>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 92ce772..c6779fa 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -4,16 +4,22 @@
<string name="string_cancel">Cancel</string>
<string name="string_continue">Continue</string>
<string name="string_more_options">More options</string>
- <string name="string_create_at_another_place">Create at another place</string>
+ <string name="string_create_in_another_place">Create in another place</string>
+ <string name="string_save_to_another_place">Save to another place</string>
<string name="string_no_thanks">No thanks</string>
<string name="passkey_creation_intro_title">A simple way to sign in safely</string>
<string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
<string name="choose_provider_title">Choose your default provider</string>
<string name="choose_provider_body">This provider will store passkeys and passwords for you and help you easily autofill and sign in. Learn more</string>
- <string name="choose_create_option_title">Create a passkey at</string>
+ <string name="choose_create_option_passkey_title">Create a passkey in <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
+ <string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
+ <string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
<string name="choose_sign_in_title">Use saved sign in</string>
<string name="create_passkey_at">Create passkey at</string>
- <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoName">%1$s</xliff:g> for all your sign-ins?</string>
+ <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for all your sign-ins?</string>
<string name="set_as_default">Set as default</string>
<string name="use_once">Use once</string>
+ <string name="choose_create_option_description">You can use saved <xliff:g id="type">%1$s</xliff:g> on any device. It will be saved to <xliff:g id="providerInfoDisplayName">%2$s</xliff:g> for <xliff:g id="createInfoDisplayName">%3$s</xliff:g></string>
+ <string name="more_options_title_multiple_options"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for <xliff:g id="createInfoTitle">%2$s</xliff:g></string>
+ <string name="more_options_title_one_option"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g></string>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 64a12cd..56fb1a9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -36,6 +36,7 @@
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
+import com.android.credentialmanager.jetpack.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
// Consider repo per screen, similar to view model?
class CredentialManagerRepo(
@@ -70,11 +71,12 @@
resultReceiver?.send(BaseDialogResult.RESULT_CODE_DIALOG_CANCELED, resultData)
}
- fun onOptionSelected(providerPackageName: String, entryId: Int) {
+ fun onOptionSelected(providerPackageName: String, entryKey: String, entrySubkey: String) {
val userSelectionDialogResult = UserSelectionDialogResult(
requestInfo.token,
providerPackageName,
- entryId
+ entryKey,
+ entrySubkey
)
val resultData = Bundle()
UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultData)
@@ -83,17 +85,28 @@
fun getCredentialInitialUiState(): GetCredentialUiState {
val providerList = GetFlowUtils.toProviderList(providerList, context)
+ // TODO: covert from real requestInfo
+ val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo(
+ "Elisa Beckett",
+ "beckett-bakert@gmail.com",
+ TYPE_PUBLIC_KEY_CREDENTIAL,
+ "tribank")
return GetCredentialUiState(
providerList,
GetScreenState.CREDENTIAL_SELECTION,
+ requestDisplayInfo,
providerList.first()
)
}
fun createPasskeyInitialUiState(): CreatePasskeyUiState {
val providerList = CreateFlowUtils.toProviderList(providerList, context)
+ // TODO: covert from real requestInfo
val requestDisplayInfo = RequestDisplayInfo(
- "Elisa Beckett", "beckett-bakert@gmail.com", "TYPE_CREATE")
+ "Elisa Beckett",
+ "beckett-bakert@gmail.com",
+ TYPE_PUBLIC_KEY_CREDENTIAL,
+ "tribank")
return CreatePasskeyUiState(
providers = providerList,
currentScreenState = CreateScreenState.PASSKEY_INTRO,
@@ -125,16 +138,16 @@
Icon.createWithResource(context, R.drawable.ic_launcher_foreground))
.setCredentialEntries(
listOf<Entry>(
- newEntry(1, "elisa.beckett@gmail.com", "Elisa Backett",
- "20 passwords and 7 passkeys saved"),
- newEntry(2, "elisa.work@google.com", "Elisa Backett Work",
- "20 passwords and 7 passkeys saved"),
+ newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
+ "Elisa Backett", "20 passwords and 7 passkeys saved"),
+ newEntry("key1", "subkey-2", "elisa.work@google.com",
+ "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
)
).setActionChips(
listOf<Entry>(
- newEntry(3, "Go to Settings", "",
+ newEntry("key2", "subkey-1", "Go to Settings", "",
"20 passwords and 7 passkeys saved"),
- newEntry(4, "Switch Account", "",
+ newEntry("key2", "subkey-2", "Switch Account", "",
"20 passwords and 7 passkeys saved"),
),
).build(),
@@ -144,21 +157,28 @@
Icon.createWithResource(context, R.drawable.ic_launcher_foreground))
.setCredentialEntries(
listOf<Entry>(
- newEntry(1, "elisa.beckett@dashlane.com", "Elisa Backett",
- "20 passwords and 7 passkeys saved"),
- newEntry(2, "elisa.work@dashlane.com", "Elisa Backett Work",
- "20 passwords and 7 passkeys saved"),
+ newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
+ "Elisa Backett", "20 passwords and 7 passkeys saved"),
+ newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
+ "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
)
).setActionChips(
listOf<Entry>(
- newEntry(3, "Manage Accounts", "Manage your accounts in the dashlane app",
+ newEntry("key2", "subkey-3", "Manage Accounts",
+ "Manage your accounts in the dashlane app",
"20 passwords and 7 passkeys saved"),
),
).build(),
)
}
- private fun newEntry(id: Int, title: String, subtitle: String, usageData: String): Entry {
+ private fun newEntry(
+ key: String,
+ subkey: String,
+ title: String,
+ subtitle: String,
+ usageData: String
+ ): Entry {
val slice = Slice.Builder(
Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
)
@@ -171,7 +191,8 @@
.addText(usageData, Slice.SUBTYPE_MESSAGE, listOf(Entry.HINT_SUBTITLE))
.build()
return Entry(
- id,
+ key,
+ subkey,
slice
)
}
@@ -182,7 +203,7 @@
Binder(),
CreateCredentialRequest(
// TODO: use the jetpack type and utils once defined.
- "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
+ TYPE_PUBLIC_KEY_CREDENTIAL,
data
),
/*isFirstUsage=*/false,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 6b503ff..e037db7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -36,7 +36,7 @@
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
name = it.providerId,
- appDomainName = "tribank.us",
+ displayName = it.providerDisplayName,
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context)
)
@@ -59,7 +59,8 @@
?: context.getDrawable(R.drawable.ic_passkey)!!,
title = credentialEntryUi.userName.toString(),
subtitle = credentialEntryUi.displayName?.toString() ?: "Unknown display name",
- id = it.entryId,
+ entryKey = it.key,
+ entrySubkey = it.subkey,
usageData = credentialEntryUi.usageData?.toString() ?: "Unknown usageData",
)
}
@@ -79,7 +80,7 @@
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
name = it.providerId,
- appDomainName = "tribank.us",
+ displayName = it.providerDisplayName,
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
createOptions = toCreationOptionInfoList(it.credentialEntries, context),
)
@@ -99,7 +100,8 @@
?: context.getDrawable(R.drawable.ic_passkey)!!,
title = saveEntryUi.title.toString(),
subtitle = saveEntryUi.subTitle?.toString() ?: "Unknown subtitle",
- id = it.entryId,
+ entryKey = it.key,
+ entrySubkey = it.subkey,
usageData = saveEntryUi.usageData?.toString() ?: "Unknown usageData",
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index e291cc2..cb2bf10 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -21,7 +21,7 @@
data class ProviderInfo(
val icon: Drawable,
val name: String,
- val appDomainName: String,
+ val displayName: String,
val credentialTypeIcon: Drawable,
val createOptions: List<CreateOptionInfo>,
)
@@ -30,7 +30,8 @@
val icon: Drawable,
val title: String,
val subtitle: String,
- val id: Int,
+ val entryKey: String,
+ val entrySubkey: String,
val usageData: String
)
@@ -38,6 +39,7 @@
val userName: String,
val displayName: String,
val type: String,
+ val appDomainName: String,
)
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index fbbc3ac..8e30208 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -6,7 +6,6 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
@@ -23,6 +22,7 @@
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.Text
+import androidx.compose.material.TextButton
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
@@ -39,6 +39,8 @@
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.R
+import com.android.credentialmanager.jetpack.CredentialEntryUi.Companion.TYPE_PASSWORD_CREDENTIAL
+import com.android.credentialmanager.jetpack.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
import com.android.credentialmanager.ui.theme.Grey100
import com.android.credentialmanager.ui.theme.Shapes
import com.android.credentialmanager.ui.theme.Typography
@@ -61,30 +63,32 @@
val uiState = viewModel.uiState
when (uiState.currentScreenState) {
CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
- onConfirm = {viewModel.onConfirmIntro()},
- onCancel = {viewModel.onCancel()},
+ onConfirm = viewModel::onConfirmIntro,
+ onCancel = viewModel::onCancel,
)
CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
providerList = uiState.providers,
- onCancel = {viewModel.onCancel()},
- onProviderSelected = {viewModel.onProviderSelected(it)}
+ onCancel = viewModel::onCancel,
+ onProviderSelected = viewModel::onProviderSelected
)
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
providerInfo = uiState.activeEntry?.activeProvider!!,
- onOptionSelected = {viewModel.onPrimaryCreateOptionInfoSelected()},
- onCancel = {viewModel.onCancel()},
+ createOptionInfo = uiState.activeEntry.activeCreateOptionInfo,
+ onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
+ onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
+ onCancel = viewModel::onCancel,
multiProvider = uiState.providers.size > 1,
- onMoreOptionsSelected = {viewModel.onMoreOptionsSelected()}
+ onMoreOptionsSelected = viewModel::onMoreOptionsSelected
)
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
providerList = uiState.providers,
- onBackButtonSelected = {viewModel.onBackButtonSelected()},
- onOptionSelected = {viewModel.onMoreOptionsRowSelected(it)}
+ onBackButtonSelected = viewModel::onBackButtonSelected,
+ onOptionSelected = viewModel::onMoreOptionsRowSelected
)
CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
providerInfo = uiState.activeEntry?.activeProvider!!,
- onDefaultOrNotSelected = {viewModel.onDefaultOrNotSelected()}
+ onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
)
}
},
@@ -294,7 +298,7 @@
) {
Column() {
Text(
- text = stringResource(R.string.use_provider_for_all_title, providerInfo.name),
+ text = stringResource(R.string.use_provider_for_all_title, providerInfo.displayName),
style = Typography.subtitle1,
modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
)
@@ -340,7 +344,7 @@
shape = Shapes.large
) {
Text(
- text = providerInfo.name,
+ text = providerInfo.displayName,
style = Typography.button,
modifier = Modifier.padding(vertical = 18.dp)
)
@@ -392,7 +396,9 @@
fun CreationSelectionCard(
requestDisplayInfo: RequestDisplayInfo,
providerInfo: ProviderInfo,
+ createOptionInfo: CreateOptionInfo,
onOptionSelected: () -> Unit,
+ onConfirm: () -> Unit,
onCancel: () -> Unit,
multiProvider: Boolean,
onMoreOptionsSelected: () -> Unit,
@@ -405,21 +411,39 @@
bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
contentDescription = null,
tint = Color.Unspecified,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
)
Text(
- text = "${stringResource(R.string.choose_create_option_title)} ${providerInfo.name}",
+ text = when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.choose_create_option_passkey_title,
+ providerInfo.displayName)
+ TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.choose_create_option_password_title,
+ providerInfo.displayName)
+ else -> stringResource(R.string.choose_create_option_sign_in_title,
+ providerInfo.displayName)
+ },
style = Typography.subtitle1,
- modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+ modifier = Modifier.padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ textAlign = TextAlign.Center,
)
Text(
- text = providerInfo.appDomainName,
+ text = requestDisplayInfo.appDomainName,
style = Typography.body2,
- modifier = Modifier.padding(horizontal = 28.dp)
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
)
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
+ Text(
+ text = stringResource(
+ R.string.choose_create_option_description,
+ when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL -> "passkeys"
+ TYPE_PASSWORD_CREDENTIAL -> "passwords"
+ else -> "sign-ins"
+ },
+ providerInfo.displayName,
+ createOptionInfo.title),
+ style = Typography.body1,
+ modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
)
Card(
shape = Shapes.medium,
@@ -434,11 +458,22 @@
PrimaryCreateOptionRow(requestDisplayInfo = requestDisplayInfo,
onOptionSelected = onOptionSelected)
}
- if (multiProvider) {
- item {
- MoreOptionsRow(onSelect = onMoreOptionsSelected)
- }
- }
+ }
+ }
+ if (multiProvider) {
+ TextButton(
+ onClick = onMoreOptionsSelected,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)){
+ Text(
+ text =
+ when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL ->
+ stringResource(R.string.string_create_in_another_place)
+ else -> stringResource(R.string.string_save_to_another_place)},
+ textAlign = TextAlign.Center,
+ )
}
}
Divider(
@@ -446,10 +481,17 @@
color = Color.Transparent
)
Row(
- horizontalArrangement = Arrangement.Start,
+ horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
- CancelButton(stringResource(R.string.string_cancel), onCancel)
+ CancelButton(
+ stringResource(R.string.string_cancel),
+ onclick = onCancel
+ )
+ ConfirmButton(
+ stringResource(R.string.string_continue),
+ onclick = onConfirm
+ )
}
Divider(
thickness = 18.dp,
@@ -462,47 +504,13 @@
@ExperimentalMaterialApi
@Composable
-fun CreateOptionRow(createOptionInfo: CreateOptionInfo, onOptionSelected: (Int) -> Unit) {
- Chip(
- modifier = Modifier.fillMaxWidth(),
- onClick = {onOptionSelected(createOptionInfo.id)},
- leadingIcon = {
- Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
- bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(),
- // painter = painterResource(R.drawable.ic_passkey),
- // TODO: add description.
- contentDescription = "")
- },
- colors = ChipDefaults.chipColors(
- backgroundColor = Grey100,
- leadingIconContentColor = Grey100
- ),
- shape = Shapes.large
- ) {
- Column() {
- Text(
- text = createOptionInfo.title,
- style = Typography.h6,
- modifier = Modifier.padding(top = 16.dp)
- )
- Text(
- text = createOptionInfo.subtitle,
- style = Typography.body2,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- }
- }
-}
-
-@ExperimentalMaterialApi
-@Composable
fun PrimaryCreateOptionRow(
requestDisplayInfo: RequestDisplayInfo,
onOptionSelected: () -> Unit
) {
Chip(
modifier = Modifier.fillMaxWidth(),
- onClick = {onOptionSelected()},
+ onClick = onOptionSelected,
// TODO: Add an icon generated by provider according to requestDisplayInfo type
colors = ChipDefaults.chipColors(
backgroundColor = Grey100,
@@ -550,8 +558,12 @@
) {
Column() {
Text(
- text = if (providerInfo.createOptions.size > 1)
- {providerInfo.name + " for " + createOptionInfo.title} else { providerInfo.name},
+ text =
+ if (providerInfo.createOptions.size > 1)
+ {stringResource(R.string.more_options_title_multiple_options,
+ providerInfo.displayName, createOptionInfo.title)} else {
+ stringResource(R.string.more_options_title_one_option,
+ providerInfo.displayName)},
style = Typography.h6,
modifier = Modifier.padding(top = 16.dp)
)
@@ -562,23 +574,4 @@
)
}
}
-}
-
-@ExperimentalMaterialApi
-@Composable
-fun MoreOptionsRow(onSelect: () -> Unit) {
- Chip(
- modifier = Modifier.fillMaxWidth().height(52.dp),
- onClick = onSelect,
- colors = ChipDefaults.chipColors(
- backgroundColor = Grey100,
- leadingIconContentColor = Grey100
- ),
- shape = Shapes.large
- ) {
- Text(
- text = stringResource(R.string.string_create_at_another_place),
- style = Typography.h6,
- )
- }
-}
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index 38486e2c..615da4e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -73,11 +73,15 @@
)
}
- fun onCreateOptionSelected(createOptionId: Int) {
- Log.d("Account Selector", "Option selected for creation: $createOptionId")
+ fun onCreateOptionSelected(entryKey: String, entrySubkey: String) {
+ Log.d(
+ "Account Selector",
+ "Option selected for creation: {key = $entryKey, subkey = $entrySubkey}"
+ )
CredentialManagerRepo.getInstance().onOptionSelected(
uiState.activeEntry?.activeProvider!!.name,
- createOptionId
+ entryKey,
+ entrySubkey
)
dialogResult.value = DialogResult(
ResultState.COMPLETE,
@@ -122,13 +126,21 @@
}
fun onPrimaryCreateOptionInfoSelected() {
- var createOptionId = uiState.activeEntry?.activeCreateOptionInfo?.id
- Log.d("Account Selector", "Option selected for creation: $createOptionId")
- if (createOptionId != null) {
+ var createOptionEntryKey = uiState.activeEntry?.activeCreateOptionInfo?.entryKey
+ var createOptionEntrySubkey = uiState.activeEntry?.activeCreateOptionInfo?.entrySubkey
+ Log.d(
+ "Account Selector",
+ "Option selected for creation: " +
+ "{key = $createOptionEntryKey, subkey = $createOptionEntrySubkey}"
+ )
+ if (createOptionEntryKey != null && createOptionEntrySubkey != null) {
CredentialManagerRepo.getInstance().onOptionSelected(
uiState.activeEntry?.activeProvider!!.name,
- createOptionId
+ createOptionEntryKey,
+ createOptionEntrySubkey
)
+ } else {
+ TODO("Gracefully handle illegal state.")
}
dialogResult.value = DialogResult(
ResultState.COMPLETE,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 48c67bb..e3398c0 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -66,11 +66,12 @@
val uiState = viewModel.uiState
when (uiState.currentScreenState) {
GetScreenState.CREDENTIAL_SELECTION -> CredentialSelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
providerInfo = uiState.selectedProvider!!,
- onCancel = {viewModel.onCancel()},
- onOptionSelected = {viewModel.onCredentailSelected(it)},
+ onCancel = viewModel::onCancel,
+ onOptionSelected = viewModel::onCredentailSelected,
multiProvider = uiState.providers.size > 1,
- onMoreOptionSelected = {viewModel.onMoreOptionSelected()},
+ onMoreOptionSelected = viewModel::onMoreOptionSelected,
)
}
},
@@ -87,8 +88,9 @@
@ExperimentalMaterialApi
@Composable
fun CredentialSelectionCard(
+ requestDisplayInfo: RequestDisplayInfo,
providerInfo: ProviderInfo,
- onOptionSelected: (Int) -> Unit,
+ onOptionSelected: (String, String) -> Unit,
onCancel: () -> Unit,
multiProvider: Boolean,
onMoreOptionSelected: () -> Unit,
@@ -111,7 +113,7 @@
.align(alignment = Alignment.CenterHorizontally)
)
Text(
- text = providerInfo.appDomainName,
+ text = requestDisplayInfo.appDomainName,
style = Typography.body2,
modifier = Modifier.padding(horizontal = 28.dp)
)
@@ -163,11 +165,11 @@
@Composable
fun CredentialOptionRow(
credentialOptionInfo: CredentialOptionInfo,
- onOptionSelected: (Int) -> Unit
+ onOptionSelected: (String, String) -> Unit,
) {
Chip(
modifier = Modifier.fillMaxWidth(),
- onClick = {onOptionSelected(credentialOptionInfo.id)},
+ onClick = {onOptionSelected(credentialOptionInfo.entryKey, credentialOptionInfo.entrySubkey)},
leadingIcon = {
Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
bitmap = credentialOptionInfo.icon.toBitmap().asImageBitmap(),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 33858f5..7b6c30a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -30,6 +30,7 @@
data class GetCredentialUiState(
val providers: List<ProviderInfo>,
val currentScreenState: GetScreenState,
+ val requestDisplayInfo: RequestDisplayInfo,
val selectedProvider: ProviderInfo? = null,
)
@@ -48,11 +49,12 @@
return dialogResult
}
- fun onCredentailSelected(credentialId: Int) {
- Log.d("Account Selector", "credential selected: $credentialId")
+ fun onCredentailSelected(entryKey: String, entrySubkey: String) {
+ Log.d("Account Selector", "credential selected: {key=$entryKey,subkey=$entrySubkey}")
CredentialManagerRepo.getInstance().onOptionSelected(
uiState.selectedProvider!!.name,
- credentialId
+ entryKey,
+ entrySubkey
)
dialogResult.value = DialogResult(
ResultState.COMPLETE,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index a39b211..b6ecd37 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -21,7 +21,7 @@
data class ProviderInfo(
val icon: Drawable,
val name: String,
- val appDomainName: String,
+ val displayName: String,
val credentialTypeIcon: Drawable,
val credentialOptions: List<CredentialOptionInfo>,
)
@@ -30,10 +30,18 @@
val icon: Drawable,
val title: String,
val subtitle: String,
- val id: Int,
+ val entryKey: String,
+ val entrySubkey: String,
val usageData: String
)
+data class RequestDisplayInfo(
+ val userName: String,
+ val displayName: String,
+ val type: String,
+ val appDomainName: String,
+)
+
/** The name of the current screen. */
enum class GetScreenState {
CREDENTIAL_SELECTION,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt
new file mode 100644
index 0000000..d4341b4
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack
+
+import android.app.slice.Slice
+import android.credentials.ui.Entry
+import android.graphics.drawable.Icon
+
+/**
+ * UI representation for a credential entry used during the get credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+class ActionUi(
+ val icon: Icon,
+ val text: CharSequence,
+ val subtext: CharSequence?,
+) {
+ companion object {
+ fun fromSlice(slice: Slice): ActionUi {
+ var icon: Icon? = null
+ var text: CharSequence? = null
+ var subtext: CharSequence? = null
+
+ val items = slice.items
+ items.forEach {
+ if (it.hasHint(Entry.HINT_ACTION_ICON)) {
+ icon = it.icon
+ } else if (it.hasHint(Entry.HINT_ACTION_TITLE)) {
+ text = it.text
+ } else if (it.hasHint(Entry.HINT_ACTION_SUBTEXT)) {
+ subtext = it.text
+ }
+ }
+ // TODO: fail NPE more elegantly.
+ return ActionUi(icon!!, text!!, subtext)
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 68c63da..4fb77d7 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -18,9 +18,8 @@
ext {
spa_min_sdk = 21
spa_target_sdk = 33
- jetpack_compose_version = '1.2.0-alpha04'
+ jetpack_compose_version = '1.3.0'
jetpack_compose_compiler_version = '1.3.2'
- jetpack_compose_material3_version = '1.0.0-alpha06'
}
}
plugins {
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index c1ce7d9..20dd707 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -54,11 +54,6 @@
composeOptions {
kotlinCompilerExtensionVersion jetpack_compose_compiler_version
}
- packagingOptions {
- resources {
- excludes += '/META-INF/{AL2.0,LGPL2.1}'
- }
- }
}
dependencies {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
index e09ebda..b38178b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -17,7 +17,10 @@
package com.android.settingslib.spa.gallery.page
import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPage
@@ -48,9 +51,11 @@
@Composable
override fun Page(arguments: Bundle?) {
- SettingsScaffold(title = TITLE) {
- SettingsPager(listOf("Personal", "Work")) {
- PlaceholderTitle("Page $it")
+ SettingsScaffold(title = TITLE) { paddingValues ->
+ Box(Modifier.padding(paddingValues)) {
+ SettingsPager(listOf("Personal", "Work")) {
+ PlaceholderTitle("Page $it")
+ }
}
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index a2a913f..fa8d51c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -27,6 +27,7 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -104,6 +105,7 @@
entryList.add(
createEntry(EntryEnum.DISABLED_PREFERENCE)
.setIsAllowSearch(true)
+ .setHasMutableStatus(true)
.setMacro {
spaLogger.message(TAG, "create macro for ${EntryEnum.DISABLED_PREFERENCE}")
SimplePreferenceMacro(
@@ -113,14 +115,17 @@
icon = Icons.Outlined.DisabledByDefault,
)
}
+ .setStatusDataFn { EntryStatusData(isDisabled = true) }
.build()
)
entryList.add(
createEntry(EntryEnum.ASYNC_SUMMARY_PREFERENCE)
.setIsAllowSearch(true)
+ .setHasMutableStatus(true)
.setSearchDataFn {
EntrySearchData(title = ASYNC_PREFERENCE_TITLE)
}
+ .setStatusDataFn { EntryStatusData(isDisabled = false) }
.setUiLayoutFn {
val model = PreferencePageModel.create()
val asyncSummary = remember { model.getAsyncSummary() }
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index c587411..3b159e9 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -51,22 +51,17 @@
composeOptions {
kotlinCompilerExtensionVersion jetpack_compose_compiler_version
}
- packagingOptions {
- resources {
- excludes += '/META-INF/{AL2.0,LGPL2.1}'
- }
- }
}
dependencies {
api "androidx.appcompat:appcompat:1.7.0-alpha01"
- api "androidx.compose.material3:material3:$jetpack_compose_material3_version"
+ api "androidx.compose.material3:material3:1.1.0-alpha01"
api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
- api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha02"
+ api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha03"
api "androidx.navigation:navigation-compose:2.5.0"
- api "com.google.android.material:material:1.6.1"
+ api "com.google.android.material:material:1.7.0-alpha03"
debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
implementation "com.airbnb.android:lottie-compose:5.2.0"
}
diff --git a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml b/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
deleted file mode 100644
index 67dd2b0..0000000
--- a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 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>
-
- <style name="Theme.SpaLib.DayNight" />
-</resources>
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index e0e5fc2..25846ec 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -16,12 +16,10 @@
-->
<resources>
- <style name="Theme.SpaLib" parent="Theme.Material3.DayNight.NoActionBar">
+ <style name="Theme.SpaLib" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
- </style>
-
- <style name="Theme.SpaLib.DayNight">
- <item name="android:windowLightStatusBar">true</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
</style>
</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 89daeb1..c3c90ab 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -27,6 +27,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.navigation.NavGraph.Companion.findStartDestination
@@ -35,6 +36,7 @@
import androidx.navigation.compose.rememberNavController
import com.android.settingslib.spa.R
import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.LocalNavController
@@ -44,7 +46,6 @@
import com.android.settingslib.spa.framework.util.navRoute
private const val TAG = "BrowseActivity"
-private const val NULL_PAGE_NAME = "NULL"
/**
* The Activity to render ALL SPA pages, and handles jumps between SPA pages.
@@ -66,8 +67,9 @@
private val spaEnvironment get() = SpaEnvironmentFactory.instance
override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(R.style.Theme_SpaLib_DayNight)
+ setTheme(R.style.Theme_SpaLib)
super.onCreate(savedInstanceState)
+ WindowCompat.setDecorFitsSystemWindows(window, false)
spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
setContent {
@@ -81,36 +83,21 @@
private fun MainContent() {
val sppRepository by spaEnvironment.pageProviderRepository
val navController = rememberNavController()
+ val nullPage = SettingsPage.createNull()
CompositionLocalProvider(navController.localNavController()) {
- NavHost(navController, NULL_PAGE_NAME) {
- composable(NULL_PAGE_NAME) {}
+ NavHost(
+ navController = navController,
+ startDestination = nullPage.sppName,
+ ) {
+ composable(nullPage.sppName) {}
for (spp in sppRepository.getAllProviders()) {
composable(
route = spp.name + spp.parameter.navRoute(),
arguments = spp.parameter,
) { navBackStackEntry ->
- val lifecycleOwner = LocalLifecycleOwner.current
- val sp = remember(navBackStackEntry.arguments) {
+ PageLogger(remember(navBackStackEntry.arguments) {
spp.createSettingsPage(arguments = navBackStackEntry.arguments)
- }
-
- DisposableEffect(lifecycleOwner) {
- val observer = LifecycleEventObserver { _, event ->
- if (event == Lifecycle.Event.ON_START) {
- sp.enterPage()
- } else if (event == Lifecycle.Event.ON_STOP) {
- sp.leavePage()
- }
- }
-
- // Add the observer to the lifecycle
- lifecycleOwner.lifecycle.addObserver(observer)
-
- // When the effect leaves the Composition, remove the observer
- onDispose {
- lifecycleOwner.lifecycle.removeObserver(observer)
- }
- }
+ })
spp.Page(navBackStackEntry.arguments)
}
@@ -121,6 +108,28 @@
}
@Composable
+ private fun PageLogger(settingsPage: SettingsPage) {
+ val lifecycleOwner = LocalLifecycleOwner.current
+ DisposableEffect(lifecycleOwner) {
+ val observer = LifecycleEventObserver { _, event ->
+ if (event == Lifecycle.Event.ON_START) {
+ settingsPage.enterPage()
+ } else if (event == Lifecycle.Event.ON_STOP) {
+ settingsPage.leavePage()
+ }
+ }
+
+ // Add the observer to the lifecycle
+ lifecycleOwner.lifecycle.addObserver(observer)
+
+ // When the effect leaves the Composition, remove the observer
+ onDispose {
+ lifecycleOwner.lifecycle.removeObserver(observer)
+ }
+ }
+ }
+
+ @Composable
private fun InitialDestinationNavigator() {
val sppRepository by spaEnvironment.pageProviderRepository
val destinationNavigated = rememberSaveable { mutableStateOf(false) }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
index d631708..38f41bc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
@@ -42,9 +42,10 @@
* For gallery, AuthorityPath = com.android.spa.gallery.provider
* For Settings, AuthorityPath = com.android.settings.spa.provider
* Some examples:
- * $ adb shell content query --uri content://<AuthorityPath>/search_sitemap
* $ adb shell content query --uri content://<AuthorityPath>/search_static
* $ adb shell content query --uri content://<AuthorityPath>/search_dynamic
+ * $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status
+ * $ adb shell content query --uri content://<AuthorityPath>/search_immutable_status
*/
open class EntryProvider : ContentProvider() {
private val spaEnvironment get() = SpaEnvironmentFactory.instance
@@ -81,9 +82,10 @@
override fun attachInfo(context: Context?, info: ProviderInfo?) {
if (info != null) {
- QueryEnum.SEARCH_SITEMAP_QUERY.addUri(uriMatcher, info.authority)
QueryEnum.SEARCH_STATIC_DATA_QUERY.addUri(uriMatcher, info.authority)
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.addUri(uriMatcher, info.authority)
+ QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.addUri(uriMatcher, info.authority)
+ QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.addUri(uriMatcher, info.authority)
}
super.attachInfo(context, info)
}
@@ -97,9 +99,12 @@
): Cursor? {
return try {
when (uriMatcher.match(uri)) {
- QueryEnum.SEARCH_SITEMAP_QUERY.queryMatchCode -> querySearchSitemap()
QueryEnum.SEARCH_STATIC_DATA_QUERY.queryMatchCode -> querySearchStaticData()
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.queryMatchCode -> querySearchDynamicData()
+ QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.queryMatchCode ->
+ querySearchMutableStatusData()
+ QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.queryMatchCode ->
+ querySearchImmutableStatusData()
else -> throw UnsupportedOperationException("Unknown Uri $uri")
}
} catch (e: UnsupportedOperationException) {
@@ -110,18 +115,22 @@
}
}
- private fun querySearchSitemap(): Cursor {
+ private fun querySearchImmutableStatusData(): Cursor {
val entryRepository by spaEnvironment.entryRepository
- val cursor = MatrixCursor(QueryEnum.SEARCH_SITEMAP_QUERY.getColumns())
+ val cursor = MatrixCursor(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
- if (!entry.isAllowSearch) continue
- val intent = entry.containerPage()
- .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
- ?: Intent()
- cursor.newRow()
- .add(ColumnEnum.ENTRY_ID.id, entry.id)
- .add(ColumnEnum.ENTRY_HIERARCHY_PATH.id, entryRepository.getEntryPath(entry.id))
- .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME))
+ if (!entry.isAllowSearch || entry.mutableStatus) continue
+ fetchStatusData(entry, cursor)
+ }
+ return cursor
+ }
+
+ private fun querySearchMutableStatusData(): Cursor {
+ val entryRepository by spaEnvironment.entryRepository
+ val cursor = MatrixCursor(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.getColumns())
+ for (entry in entryRepository.getAllEntries()) {
+ if (!entry.isAllowSearch || !entry.mutableStatus) continue
+ fetchStatusData(entry, cursor)
}
return cursor
}
@@ -147,14 +156,28 @@
}
private fun fetchSearchData(entry: SettingsEntry, cursor: MatrixCursor) {
+ val entryRepository by spaEnvironment.entryRepository
+ val browseActivityClass = spaEnvironment.browseActivityClass
+
// Fetch search data. We can add runtime arguments later if necessary
- val searchData = entry.getSearchData()
+ val searchData = entry.getSearchData() ?: return
+ val intent = entry.containerPage()
+ .createBrowseIntent(context, browseActivityClass, entry.id)
+ ?: Intent()
cursor.newRow()
.add(ColumnEnum.ENTRY_ID.id, entry.id)
- .add(ColumnEnum.ENTRY_TITLE.id, searchData?.title ?: "")
- .add(
- ColumnEnum.ENTRY_SEARCH_KEYWORD.id,
- searchData?.keyword ?: emptyList<String>()
- )
+ .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME))
+ .add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
+ .add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
+ .add(ColumnEnum.SEARCH_PATH.id,
+ entryRepository.getEntryPathWithTitle(entry.id, searchData.title))
+ }
+
+ private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
+ // Fetch status data. We can add runtime arguments later if necessary
+ val statusData = entry.getStatusData() ?: return
+ cursor.newRow()
+ .add(ColumnEnum.ENTRY_ID.id, entry.id)
+ .add(ColumnEnum.SEARCH_STATUS_DISABLED.id, statusData.isDisabled)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMacro.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMacro.kt
index 9ec0c01..b3571a1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMacro.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMacro.kt
@@ -26,4 +26,5 @@
@Composable
fun UiLayout() {}
fun getSearchData(): EntrySearchData? = null
+ fun getStatusData(): EntryStatusData? = null
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySearchData.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySearchData.kt
index 9b262af..9bc620f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySearchData.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySearchData.kt
@@ -22,12 +22,4 @@
data class EntrySearchData(
val title: String = "",
val keyword: List<String> = emptyList(),
-) {
- fun format(): String {
- val content = listOf(
- "search_title = $title",
- "search_keyword = $keyword",
- )
- return content.joinToString("\n")
- }
-}
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryStatusData.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
copy to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryStatusData.kt
index 581dafa3..3e9dd3b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryStatusData.kt
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package com.android.settingslib.spa.framework.common
-/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
-enum class KeyguardQuickAffordancePosition {
- BOTTOM_START,
- BOTTOM_END,
-}
+/**
+ * Defines the status data of one Settings entry, which could be changed frequently.
+ */
+data class EntryStatusData(
+ val isDisabled: Boolean = false,
+ val isSwitchOff: Boolean = false,
+)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
index 0707429..121c07f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
@@ -40,8 +40,10 @@
ENTRY_START_ADB("entryStartAdb"),
// Columns related to search
- ENTRY_TITLE("entryTitle"),
- ENTRY_SEARCH_KEYWORD("entrySearchKw"),
+ SEARCH_TITLE("searchTitle"),
+ SEARCH_KEYWORD("searchKw"),
+ SEARCH_PATH("searchPath"),
+ SEARCH_STATUS_DISABLED("searchDisabled"),
}
/**
@@ -83,32 +85,42 @@
ColumnEnum.ENTRY_NAME,
ColumnEnum.ENTRY_ROUTE,
ColumnEnum.ENTRY_INTENT_URI,
+ ColumnEnum.ENTRY_HIERARCHY_PATH,
)
),
- // Search related queries
- SEARCH_SITEMAP_QUERY(
- "search_sitemap", 300,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.ENTRY_HIERARCHY_PATH,
- ColumnEnum.ENTRY_INTENT_URI,
- )
- ),
SEARCH_STATIC_DATA_QUERY(
"search_static", 301,
listOf(
ColumnEnum.ENTRY_ID,
- ColumnEnum.ENTRY_TITLE,
- ColumnEnum.ENTRY_SEARCH_KEYWORD,
+ ColumnEnum.ENTRY_INTENT_URI,
+ ColumnEnum.SEARCH_TITLE,
+ ColumnEnum.SEARCH_KEYWORD,
+ ColumnEnum.SEARCH_PATH,
)
),
SEARCH_DYNAMIC_DATA_QUERY(
"search_dynamic", 302,
listOf(
ColumnEnum.ENTRY_ID,
- ColumnEnum.ENTRY_TITLE,
- ColumnEnum.ENTRY_SEARCH_KEYWORD,
+ ColumnEnum.ENTRY_INTENT_URI,
+ ColumnEnum.SEARCH_TITLE,
+ ColumnEnum.SEARCH_KEYWORD,
+ ColumnEnum.SEARCH_PATH,
+ )
+ ),
+ SEARCH_IMMUTABLE_STATUS_DATA_QUERY(
+ "search_immutable_status", 303,
+ listOf(
+ ColumnEnum.ENTRY_ID,
+ ColumnEnum.SEARCH_STATUS_DISABLED,
+ )
+ ),
+ SEARCH_MUTABLE_STATUS_DATA_QUERY(
+ "search_mutable_status", 304,
+ listOf(
+ ColumnEnum.ENTRY_ID,
+ ColumnEnum.SEARCH_STATUS_DISABLED,
)
),
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index fb42f01..224fe1d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -65,8 +65,14 @@
* ========================================
*/
val isAllowSearch: Boolean = false,
+
+ // Indicate whether the search indexing data of entry is dynamic.
val isSearchDataDynamic: Boolean = false,
+ // Indicate whether the status of entry is mutable.
+ // If so, for instance, we'll reindex its status for search.
+ val mutableStatus: Boolean = false,
+
/**
* ========================================
* Defines entry APIs to get data here.
@@ -74,8 +80,14 @@
*/
/**
- * API to get Search related data for this entry.
- * Returns null if this entry is not available for the search at the moment.
+ * API to get the status data of the entry, such as isDisabled / isSwitchOff.
+ * Returns null if this entry do NOT have any status.
+ */
+ private val statusDataImpl: (arguments: Bundle?) -> EntryStatusData? = { null },
+
+ /**
+ * API to get Search indexing data for this entry, such as title / keyword.
+ * Returns null if this entry do NOT support search.
*/
private val searchDataImpl: (arguments: Bundle?) -> EntrySearchData? = { null },
@@ -87,21 +99,6 @@
*/
private val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {},
) {
- fun formatContent(): String {
- val content = listOf(
- "id = $id",
- "owner = ${owner.formatDisplayTitle()}",
- "linkFrom = ${fromPage?.formatDisplayTitle()}",
- "linkTo = ${toPage?.formatDisplayTitle()}",
- "${getSearchData()?.format()}",
- )
- return content.joinToString("\n")
- }
-
- fun displayTitle(): String {
- return "${owner.displayName}:$displayName"
- }
-
fun containerPage(): SettingsPage {
// The Container page of the entry, which is the from-page or
// the owner-page if from-page is unset.
@@ -116,6 +113,10 @@
return arguments
}
+ fun getStatusData(runtimeArguments: Bundle? = null): EntryStatusData? {
+ return statusDataImpl(fullArgument(runtimeArguments))
+ }
+
fun getSearchData(runtimeArguments: Bundle? = null): EntrySearchData? {
return searchDataImpl(fullArgument(runtimeArguments))
}
@@ -151,8 +152,10 @@
// Attributes
private var isAllowSearch: Boolean = false
private var isSearchDataDynamic: Boolean = false
+ private var mutableStatus: Boolean = false
// Functions
+ private var statusDataFn: (arguments: Bundle?) -> EntryStatusData? = { null }
private var searchDataFn: (arguments: Bundle?) -> EntrySearchData? = { null }
private var uiLayoutFn: (@Composable (arguments: Bundle?) -> Unit) = { }
@@ -170,8 +173,10 @@
// attributes
isAllowSearch = isAllowSearch,
isSearchDataDynamic = isSearchDataDynamic,
+ mutableStatus = mutableStatus,
// functions
+ statusDataImpl = statusDataFn,
searchDataImpl = searchDataFn,
uiLayoutImpl = uiLayoutFn,
)
@@ -201,7 +206,13 @@
return this
}
+ fun setHasMutableStatus(hasMutableStatus: Boolean): SettingsEntryBuilder {
+ this.mutableStatus = hasMutableStatus
+ return this
+ }
+
fun setMacro(fn: (arguments: Bundle?) -> EntryMacro): SettingsEntryBuilder {
+ setStatusDataFn { fn(it).getStatusData() }
setSearchDataFn { fn(it).getSearchData() }
setUiLayoutFn {
val macro = remember { fn(it) }
@@ -210,6 +221,11 @@
return this
}
+ fun setStatusDataFn(fn: (arguments: Bundle?) -> EntryStatusData?): SettingsEntryBuilder {
+ this.statusDataFn = fn
+ return this
+ }
+
fun setSearchDataFn(fn: (arguments: Bundle?) -> EntrySearchData?): SettingsEntryBuilder {
this.searchDataFn = fn
return this
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index ea20233..e63e4c9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -21,10 +21,13 @@
private const val TAG = "EntryRepository"
private const val MAX_ENTRY_SIZE = 5000
+private const val MAX_ENTRY_DEPTH = 10
data class SettingsPageWithEntry(
val page: SettingsPage,
val entries: List<SettingsEntry>,
+ // The inject entry, which to-page is current page.
+ val injectEntry: SettingsEntry,
)
/**
@@ -42,9 +45,11 @@
entryMap = mutableMapOf()
pageWithEntryMap = mutableMapOf()
+ val nullPage = SettingsPage.createNull()
val entryQueue = LinkedList<SettingsEntry>()
for (page in sppRepository.getAllRootPages()) {
- val rootEntry = SettingsEntryBuilder.createRoot(owner = page).build()
+ val rootEntry =
+ SettingsEntryBuilder.createRoot(owner = page).setLink(fromPage = nullPage).build()
if (!entryMap.containsKey(rootEntry.id)) {
entryQueue.push(rootEntry)
entryMap.put(rootEntry.id, rootEntry)
@@ -57,7 +62,11 @@
if (page == null || pageWithEntryMap.containsKey(page.id)) continue
val spp = sppRepository.getProviderOrNull(page.sppName) ?: continue
val newEntries = spp.buildEntry(page.arguments)
- pageWithEntryMap[page.id] = SettingsPageWithEntry(page, newEntries)
+ pageWithEntryMap[page.id] = SettingsPageWithEntry(
+ page = page,
+ entries = newEntries,
+ injectEntry = entry
+ )
for (newEntry in newEntries) {
if (!entryMap.containsKey(newEntry.id)) {
entryQueue.push(newEntry)
@@ -88,7 +97,29 @@
return entryMap[entryId]
}
- fun getEntryPath(entryId: String): String {
- return "TODO(path_of_$entryId)"
+ private fun getEntryPath(entryId: String): List<SettingsEntry> {
+ val entryPath = ArrayList<SettingsEntry>()
+ var currentEntry = entryMap[entryId]
+ while (currentEntry != null && entryPath.size < MAX_ENTRY_DEPTH) {
+ entryPath.add(currentEntry)
+ val currentPage = currentEntry.containerPage()
+ currentEntry = pageWithEntryMap[currentPage.id]?.injectEntry
+ }
+ return entryPath
+ }
+
+ fun getEntryPathWithDisplayName(entryId: String): List<String> {
+ val entryPath = getEntryPath(entryId)
+ return entryPath.map { it.displayName }
+ }
+
+ fun getEntryPathWithTitle(entryId: String, defaultTitle: String): List<String> {
+ val entryPath = getEntryPath(entryId)
+ return entryPath.map {
+ if (it.toPage == null)
+ defaultTitle
+ else
+ it.toPage.getTitle()!!
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 07df96e..2fa9229 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -27,6 +27,8 @@
import com.android.settingslib.spa.framework.util.navLink
import com.android.settingslib.spa.framework.util.normalize
+private const val NULL_PAGE_NAME = "NULL"
+
/**
* Defines data to identify a Settings page.
*/
@@ -47,6 +49,10 @@
val arguments: Bundle? = null,
) {
companion object {
+ fun createNull(): SettingsPage {
+ return create(NULL_PAGE_NAME)
+ }
+
fun create(
name: String,
displayName: String? = null,
@@ -78,16 +84,6 @@
return sppName == SppName
}
- fun formatArguments(): String {
- val normArguments = parameter.normalize(arguments)
- if (normArguments == null || normArguments.isEmpty) return "[No arguments]"
- return normArguments.toString().removeRange(0, 6)
- }
-
- fun formatDisplayTitle(): String {
- return "$displayName ${formatArguments()}"
- }
-
fun buildRoute(): String {
return sppName + parameter.navLink(arguments)
}
@@ -99,12 +95,17 @@
return false
}
+ fun getTitle(): String? {
+ val sppRepository by SpaEnvironmentFactory.instance.pageProviderRepository
+ return sppRepository.getProviderOrNull(sppName)?.getTitle(arguments)
+ }
+
fun enterPage() {
SpaEnvironmentFactory.instance.logger.event(
id,
LogEvent.PAGE_ENTER,
category = LogCategory.FRAMEWORK,
- details = formatDisplayTitle()
+ details = displayName,
)
}
@@ -113,7 +114,7 @@
id,
LogEvent.PAGE_LEAVE,
category = LogCategory.FRAMEWORK,
- details = formatDisplayTitle()
+ details = displayName,
)
}
@@ -149,6 +150,7 @@
fun isBrowsable(context: Context?, browseActivityClass: Class<out Activity>?): Boolean {
return context != null &&
browseActivityClass != null &&
+ !isCreateBy(NULL_PAGE_NAME) &&
!hasRuntimeParam()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index e8a4411..f8963b2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -41,6 +41,8 @@
fun Page(arguments: Bundle?)
fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
+
+ fun getTitle(arguments: Bundle?): String = displayName ?: name
}
fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt
new file mode 100644
index 0000000..dbf8836
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2022 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.settingslib.spa.framework.compose
+
+import android.annotation.SuppressLint
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.produceState
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.repeatOnLifecycle
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.withContext
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+/**
+ * *************************************************************************************************
+ * This file was forked from AndroidX:
+ * lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/FlowExt.kt
+ * TODO: Replace with AndroidX when it's usable.
+ */
+
+/**
+ * Collects values from this [StateFlow] and represents its latest value via [State] in a
+ * lifecycle-aware manner.
+ *
+ * The [StateFlow.value] is used as an initial value. Every time there would be new value posted
+ * into the [StateFlow] the returned [State] will be updated causing recomposition of every
+ * [State.value] usage whenever the [lifecycleOwner]'s lifecycle is at least [minActiveState].
+ *
+ * This [StateFlow] is collected every time the [lifecycleOwner]'s lifecycle reaches the
+ * [minActiveState] Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle
+ * falls below [minActiveState].
+ *
+ * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
+ *
+ * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
+ * parameter will throw an [IllegalArgumentException].
+ *
+ * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
+ * flow.
+ * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
+ * collection will stop if the lifecycle falls below that state, and will restart if it's in that
+ * state again.
+ * @param context [CoroutineContext] to use for collecting.
+ */
+@SuppressLint("StateFlowValueCalledInComposition")
+@Composable
+fun <T> StateFlow<T>.collectAsStateWithLifecycle(
+ lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+ minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
+ context: CoroutineContext = EmptyCoroutineContext
+): State<T> = collectAsStateWithLifecycle(
+ initialValue = this.value,
+ lifecycle = lifecycleOwner.lifecycle,
+ minActiveState = minActiveState,
+ context = context
+)
+
+/**
+ * Collects values from this [StateFlow] and represents its latest value via [State] in a
+ * lifecycle-aware manner.
+ *
+ * The [StateFlow.value] is used as an initial value. Every time there would be new value posted
+ * into the [StateFlow] the returned [State] will be updated causing recomposition of every
+ * [State.value] usage whenever the [lifecycle] is at least [minActiveState].
+ *
+ * This [StateFlow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
+ * state. The collection stops when [lifecycle] falls below [minActiveState].
+ *
+ * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
+ *
+ * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
+ * parameter will throw an [IllegalArgumentException].
+ *
+ * @param lifecycle [Lifecycle] used to restart collecting `this` flow.
+ * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
+ * collection will stop if the lifecycle falls below that state, and will restart if it's in that
+ * state again.
+ * @param context [CoroutineContext] to use for collecting.
+ */
+@SuppressLint("StateFlowValueCalledInComposition")
+@Composable
+fun <T> StateFlow<T>.collectAsStateWithLifecycle(
+ lifecycle: Lifecycle,
+ minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
+ context: CoroutineContext = EmptyCoroutineContext
+): State<T> = collectAsStateWithLifecycle(
+ initialValue = this.value,
+ lifecycle = lifecycle,
+ minActiveState = minActiveState,
+ context = context
+)
+
+/**
+ * Collects values from this [Flow] and represents its latest value via [State] in a
+ * lifecycle-aware manner.
+ *
+ * Every time there would be new value posted into the [Flow] the returned [State] will be updated
+ * causing recomposition of every [State.value] usage whenever the [lifecycleOwner]'s lifecycle is
+ * at least [minActiveState].
+ *
+ * This [Flow] is collected every time the [lifecycleOwner]'s lifecycle reaches the [minActiveState]
+ * Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle falls below
+ * [minActiveState].
+ *
+ * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
+ *
+ * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
+ * parameter will throw an [IllegalArgumentException].
+ *
+ * @param initialValue The initial value given to the returned [State.value].
+ * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
+ * flow.
+ * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
+ * collection will stop if the lifecycle falls below that state, and will restart if it's in that
+ * state again.
+ * @param context [CoroutineContext] to use for collecting.
+ */
+@Composable
+fun <T> Flow<T>.collectAsStateWithLifecycle(
+ initialValue: T,
+ lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+ minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
+ context: CoroutineContext = EmptyCoroutineContext
+): State<T> = collectAsStateWithLifecycle(
+ initialValue = initialValue,
+ lifecycle = lifecycleOwner.lifecycle,
+ minActiveState = minActiveState,
+ context = context
+)
+
+/**
+ * Collects values from this [Flow] and represents its latest value via [State] in a
+ * lifecycle-aware manner.
+ *
+ * Every time there would be new value posted into the [Flow] the returned [State] will be updated
+ * causing recomposition of every [State.value] usage whenever the [lifecycle] is at
+ * least [minActiveState].
+ *
+ * This [Flow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
+ * state. The collection stops when [lifecycle] falls below [minActiveState].
+ *
+ * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
+ *
+ * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
+ * parameter will throw an [IllegalArgumentException].
+ *
+ * @param initialValue The initial value given to the returned [State.value].
+ * @param lifecycle [Lifecycle] used to restart collecting `this` flow.
+ * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
+ * collection will stop if the lifecycle falls below that state, and will restart if it's in that
+ * state again.
+ * @param context [CoroutineContext] to use for collecting.
+ */
+@Composable
+fun <T> Flow<T>.collectAsStateWithLifecycle(
+ initialValue: T,
+ lifecycle: Lifecycle,
+ minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
+ context: CoroutineContext = EmptyCoroutineContext
+): State<T> {
+ return produceState(initialValue, this, lifecycle, minActiveState, context) {
+ lifecycle.repeatOnLifecycle(minActiveState) {
+ if (context == EmptyCoroutineContext) {
+ this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
+ } else withContext(context) {
+ this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
new file mode 100644
index 0000000..8d0313f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.compose
+
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.text.KeyboardActionScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+
+/**
+ * An action when run, hides the keyboard if it's open.
+ */
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun hideKeyboardAction(): KeyboardActionScope.() -> Unit {
+ val keyboardController = LocalSoftwareKeyboardController.current
+ return { keyboardController?.hide() }
+}
+
+/**
+ * Creates a [LazyListState] that is remembered across compositions.
+ *
+ * And when user scrolling the lazy list, hides the keyboard if it's open.
+ */
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun rememberLazyListStateAndHideKeyboardWhenStartScroll(): LazyListState {
+ val listState = rememberLazyListState()
+ val keyboardController = LocalSoftwareKeyboardController.current
+ LaunchedEffect(listState) {
+ snapshotFlow { listState.isScrollInProgress }
+ .distinctUntilChanged()
+ .filter { it }
+ .collect { keyboardController?.hide() }
+ }
+ return listState
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OverridableFlow.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OverridableFlow.kt
new file mode 100644
index 0000000..1b33dd6
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OverridableFlow.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 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.settingslib.spa.framework.compose
+
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.receiveAsFlow
+
+/**
+ * A flow which result is overridable.
+ */
+class OverridableFlow<T>(flow: Flow<T>) {
+ private val overrideChannel = Channel<T>()
+
+ val flow = merge(overrideChannel.receiveAsFlow(), flow)
+
+ fun override(value: T) {
+ overrideChannel.trySend(value)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt
new file mode 100644
index 0000000..18335ff
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.compose
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+
+internal fun PaddingValues.horizontalValues(): PaddingValues = HorizontalPaddingValues(this)
+
+internal fun PaddingValues.verticalValues(): PaddingValues = VerticalPaddingValues(this)
+
+private class HorizontalPaddingValues(private val paddingValues: PaddingValues) : PaddingValues {
+ override fun calculateLeftPadding(layoutDirection: LayoutDirection) =
+ paddingValues.calculateLeftPadding(layoutDirection)
+
+ override fun calculateTopPadding(): Dp = 0.dp
+
+ override fun calculateRightPadding(layoutDirection: LayoutDirection) =
+ paddingValues.calculateRightPadding(layoutDirection)
+
+ override fun calculateBottomPadding() = 0.dp
+}
+
+private class VerticalPaddingValues(private val paddingValues: PaddingValues) : PaddingValues {
+ override fun calculateLeftPadding(layoutDirection: LayoutDirection) = 0.dp
+
+ override fun calculateTopPadding(): Dp = paddingValues.calculateTopPadding()
+
+ override fun calculateRightPadding(layoutDirection: LayoutDirection) = 0.dp
+
+ override fun calculateBottomPadding() = paddingValues.calculateBottomPadding()
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
index bf33857..4df7794 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
@@ -40,7 +40,6 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filter
@@ -214,6 +213,7 @@
horizontalAlignment = horizontalAlignment,
reverseLayout = reverseLayout,
contentPadding = contentPadding,
+ userScrollEnabled = false,
modifier = modifier,
) {
items(
@@ -241,6 +241,7 @@
horizontalArrangement = Arrangement.spacedBy(itemSpacing, horizontalAlignment),
reverseLayout = reverseLayout,
contentPadding = contentPadding,
+ userScrollEnabled = false,
modifier = modifier,
) {
items(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
index 3015080..26491d5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
@@ -62,7 +62,7 @@
private val spaEnvironment get() = SpaEnvironmentFactory.instance
override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(R.style.Theme_SpaLib_DayNight)
+ setTheme(R.style.Theme_SpaLib)
super.onCreate(savedInstanceState)
spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
@@ -120,12 +120,11 @@
val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() }
RegularScaffold(title = "All Pages (${allPageWithEntry.size})") {
for (pageWithEntry in allPageWithEntry) {
+ val page = pageWithEntry.page
Preference(object : PreferenceModel {
- override val title =
- "${pageWithEntry.page.displayName} (${pageWithEntry.entries.size})"
- override val summary = pageWithEntry.page.formatArguments().toState()
- override val onClick =
- navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id}")
+ override val title = "${page.debugBrief()} (${pageWithEntry.entries.size})"
+ override val summary = page.debugArguments().toState()
+ override val onClick = navigator(route = ROUTE_PAGE + "/${page.id}")
})
}
}
@@ -146,16 +145,16 @@
val entryRepository by spaEnvironment.entryRepository
val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")
val pageWithEntry = entryRepository.getPageWithEntry(id)!!
- RegularScaffold(title = "Page - ${pageWithEntry.page.displayName}") {
- Text(text = "id = ${pageWithEntry.page.id}")
- Text(text = pageWithEntry.page.formatArguments())
+ val page = pageWithEntry.page
+ RegularScaffold(title = "Page - ${page.debugBrief()}") {
+ Text(text = "id = ${page.id}")
+ Text(text = page.debugArguments())
Text(text = "Entry size: ${pageWithEntry.entries.size}")
Preference(model = object : PreferenceModel {
override val title = "open page"
override val enabled =
- pageWithEntry.page.isBrowsable(context, spaEnvironment.browseActivityClass)
- .toState()
- override val onClick = openPage(pageWithEntry.page)
+ page.isBrowsable(context, spaEnvironment.browseActivityClass).toState()
+ override val onClick = openPage(page)
})
EntryList(pageWithEntry.entries)
}
@@ -167,8 +166,8 @@
val entryRepository by spaEnvironment.entryRepository
val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "")
val entry = entryRepository.getEntry(id)!!
- val entryContent = remember { entry.formatContent() }
- RegularScaffold(title = "Entry - ${entry.displayTitle()}") {
+ val entryContent = remember { entry.debugContent(entryRepository) }
+ RegularScaffold(title = "Entry - ${entry.debugBrief()}") {
Preference(model = object : PreferenceModel {
override val title = "open entry"
override val enabled =
@@ -184,7 +183,7 @@
private fun EntryList(entries: Collection<SettingsEntry>) {
for (entry in entries) {
Preference(object : PreferenceModel {
- override val title = entry.displayTitle()
+ override val title = entry.debugBrief()
override val summary =
"${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState()
override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugFormat.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugFormat.kt
new file mode 100644
index 0000000..538d2b5
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugFormat.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.debug
+
+import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.EntryStatusData
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryRepository
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.util.normalize
+
+private fun EntrySearchData.debugContent(): String {
+ val content = listOf(
+ "search_title = $title",
+ "search_keyword = $keyword",
+ )
+ return content.joinToString("\n")
+}
+
+private fun EntryStatusData.debugContent(): String {
+ val content = listOf(
+ "is_disabled = $isDisabled",
+ "is_switch_off = $isSwitchOff",
+ )
+ return content.joinToString("\n")
+}
+
+fun SettingsPage.debugArguments(): String {
+ val normArguments = parameter.normalize(arguments)
+ if (normArguments == null || normArguments.isEmpty) return "[No arguments]"
+ return normArguments.toString().removeRange(0, 6)
+}
+
+fun SettingsPage.debugBrief(): String {
+ return displayName
+}
+
+fun SettingsEntry.debugBrief(): String {
+ return "${owner.displayName}:$displayName"
+}
+
+fun SettingsEntry.debugContent(entryRepository: SettingsEntryRepository): String {
+ val searchData = getSearchData()
+ val statusData = getStatusData()
+ val entryPathWithName = entryRepository.getEntryPathWithDisplayName(id)
+ val entryPathWithTitle = entryRepository.getEntryPathWithTitle(id,
+ searchData?.title ?: displayName)
+ val content = listOf(
+ "------ STATIC ------",
+ "id = $id",
+ "owner = ${owner.debugBrief()} ${owner.debugArguments()}",
+ "linkFrom = ${fromPage?.debugBrief()} ${fromPage?.debugArguments()}",
+ "linkTo = ${toPage?.debugBrief()} ${toPage?.debugArguments()}",
+ "hierarchy_path = $entryPathWithName",
+ "------ SEARCH ------",
+ "search_path = $entryPathWithTitle",
+ searchData?.debugContent() ?: "no search data",
+ statusData?.debugContent() ?: "no status data",
+ )
+ return content.joinToString("\n")
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt
index 6c27109..399278d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt
@@ -151,9 +151,9 @@
.add(ColumnEnum.PAGE_ID.id, page.id)
.add(ColumnEnum.PAGE_NAME.id, page.displayName)
.add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute())
+ .add(ColumnEnum.PAGE_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
.add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size)
.add(ColumnEnum.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0)
- .add(ColumnEnum.PAGE_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
}
return cursor
}
@@ -170,6 +170,8 @@
.add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
.add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute())
.add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
+ .add(ColumnEnum.ENTRY_HIERARCHY_PATH.id,
+ entryRepository.getEntryPathWithDisplayName(entry.id))
}
return cursor
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt
new file mode 100644
index 0000000..8e8805a
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+@file:OptIn(ExperimentalTextApi::class)
+
+package com.android.settingslib.spa.framework.theme
+
+import android.annotation.SuppressLint
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.font.DeviceFontFamilyName
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+
+internal data class SettingsFontFamily(
+ val brand: FontFamily = FontFamily.Default,
+ val plain: FontFamily = FontFamily.Default,
+)
+
+private fun Context.getSettingsFontFamily(inInspection: Boolean): SettingsFontFamily {
+ if (inInspection) {
+ return SettingsFontFamily()
+ }
+ return SettingsFontFamily(
+ brand = FontFamily(
+ Font(getFontFamilyName("config_headlineFontFamily"), FontWeight.Normal),
+ Font(getFontFamilyName("config_headlineFontFamilyMedium"), FontWeight.Medium),
+ ),
+ plain = FontFamily(
+ Font(getFontFamilyName("config_bodyFontFamily"), FontWeight.Normal),
+ Font(getFontFamilyName("config_bodyFontFamilyMedium"), FontWeight.Medium),
+ ),
+ )
+}
+
+private fun Context.getFontFamilyName(configName: String): DeviceFontFamilyName {
+ @SuppressLint("DiscouragedApi")
+ val configId = resources.getIdentifier(configName, "string", "android")
+ return DeviceFontFamilyName(resources.getString(configId))
+}
+
+@Composable
+internal fun rememberSettingsFontFamily(): SettingsFontFamily {
+ val context = LocalContext.current
+ val inInspection = LocalInspectionMode.current
+ return remember { context.getSettingsFontFamily(inInspection) }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
index 69ddf01..c8faef6 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
@@ -21,4 +21,5 @@
const val Disabled = 0.38f
const val Divider = 0.2f
const val SurfaceTone = 0.14f
+ const val Hint = 0.9f
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
index 07f09ba..03699bf 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
@@ -20,14 +20,13 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
-private class SettingsTypography {
- private val brand = FontFamily.Default
- private val plain = FontFamily.Default
+private class SettingsTypography(settingsFontFamily: SettingsFontFamily) {
+ private val brand = settingsFontFamily.brand
+ private val plain = settingsFontFamily.plain
val typography = Typography(
displayLarge = TextStyle(
@@ -140,5 +139,6 @@
@Composable
internal fun rememberSettingsTypography(): Typography {
- return remember { SettingsTypography().typography }
+ val settingsFontFamily = rememberSettingsFontFamily()
+ return remember { SettingsTypography(settingsFontFamily).typography }
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 6ebe6bb..895edf7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -24,6 +24,7 @@
import androidx.compose.ui.graphics.vector.ImageVector
import com.android.settingslib.spa.framework.common.EntryMacro
import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.util.wrapOnClickWithLog
@@ -55,6 +56,10 @@
keyword = searchKeywords
)
}
+
+ override fun getStatusData(): EntryStatusData {
+ return EntryStatusData(isDisabled = false)
+ }
}
/**
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index 6a88f2d..764973f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -16,9 +16,12 @@
package com.android.settingslib.spa.widget.scaffold
+import androidx.appcompat.R
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
+import androidx.compose.material.icons.outlined.Clear
+import androidx.compose.material.icons.outlined.FindInPage
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.Icon
@@ -31,17 +34,23 @@
import androidx.compose.ui.res.stringResource
import com.android.settingslib.spa.framework.compose.LocalNavController
+/** Action that navigates back to last page. */
@Composable
internal fun NavigateBack() {
val navController = LocalNavController.current
- val contentDescription = stringResource(
- id = androidx.appcompat.R.string.abc_action_bar_up_description,
- )
+ val contentDescription = stringResource(R.string.abc_action_bar_up_description)
BackAction(contentDescription) {
navController.navigateBack()
}
}
+/** Action that collapses the search bar. */
+@Composable
+internal fun CollapseAction(onClick: () -> Unit) {
+ val contentDescription = stringResource(R.string.abc_toolbar_collapse_description)
+ BackAction(contentDescription, onClick)
+}
+
@Composable
private fun BackAction(contentDescription: String, onClick: () -> Unit) {
IconButton(onClick) {
@@ -52,6 +61,28 @@
}
}
+/** Action that expends the search bar. */
+@Composable
+internal fun SearchAction(onClick: () -> Unit) {
+ IconButton(onClick) {
+ Icon(
+ imageVector = Icons.Outlined.FindInPage,
+ contentDescription = stringResource(R.string.search_menu_title),
+ )
+ }
+}
+
+/** Action that clear the search query. */
+@Composable
+internal fun ClearAction(onClick: () -> Unit) {
+ IconButton(onClick) {
+ Icon(
+ imageVector = Icons.Outlined.Clear,
+ contentDescription = stringResource(R.string.abc_searchview_description_clear),
+ )
+ }
+}
+
@Composable
fun MoreOptionsAction(
content: @Composable ColumnScope.(onDismissRequest: () -> Unit) -> Unit,
@@ -71,9 +102,7 @@
IconButton(onClick) {
Icon(
imageVector = Icons.Outlined.MoreVert,
- contentDescription = stringResource(
- id = androidx.appcompat.R.string.abc_action_menu_overflow_description,
- )
+ contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
index eb20ac5..711c8a7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
@@ -20,6 +20,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
@@ -34,6 +35,7 @@
Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.background)
+ .systemBarsPadding()
.verticalScroll(rememberScrollState()),
) {
Text(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
index 9a17b2a..d17a8dc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
@@ -19,7 +19,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold
@@ -27,6 +27,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
/**
* A [Scaffold] which content is scrollable and wrapped in a [Column].
@@ -42,8 +44,9 @@
) {
SettingsScaffold(title, actions) { paddingValues ->
Column(Modifier.verticalScroll(rememberScrollState())) {
- Spacer(Modifier.padding(paddingValues))
+ Spacer(Modifier.height(paddingValues.calculateTopPadding()))
content()
+ Spacer(Modifier.height(paddingValues.calculateBottomPadding()))
}
}
}
@@ -52,6 +55,13 @@
@Composable
private fun RegularScaffoldPreview() {
SettingsTheme {
- RegularScaffold(title = "Display") {}
+ RegularScaffold(title = "Display") {
+ Preference(object : PreferenceModel {
+ override val title = "Item 1"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Item 2"
+ })
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
new file mode 100644
index 0000000..efc623a
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.scaffold
+
+import androidx.activity.compose.BackHandler
+import androidx.appcompat.R
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.settingslib.spa.framework.compose.hideKeyboardAction
+import com.android.settingslib.spa.framework.compose.horizontalValues
+import com.android.settingslib.spa.framework.theme.SettingsOpacity
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+
+/**
+ * A [Scaffold] which content is can be full screen, and with a search feature built-in.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SearchScaffold(
+ title: String,
+ actions: @Composable RowScope.() -> Unit = {},
+ content: @Composable (bottomPadding: Dp, searchQuery: State<String>) -> Unit,
+) {
+ val viewModel: SearchScaffoldViewModel = viewModel()
+
+ val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
+ Scaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ topBar = {
+ SearchableTopAppBar(
+ title = title,
+ actions = actions,
+ scrollBehavior = scrollBehavior,
+ searchQuery = viewModel.searchQuery,
+ ) { viewModel.searchQuery = it }
+ },
+ ) { paddingValues ->
+ Box(
+ Modifier
+ .padding(paddingValues.horizontalValues())
+ .padding(top = paddingValues.calculateTopPadding())
+ .fillMaxSize(),
+ ) {
+ content(
+ bottomPadding = paddingValues.calculateBottomPadding(),
+ searchQuery = remember {
+ derivedStateOf { viewModel.searchQuery?.text ?: "" }
+ },
+ )
+ }
+ }
+}
+
+internal class SearchScaffoldViewModel : ViewModel() {
+ var searchQuery: TextFieldValue? by mutableStateOf(null)
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun SearchableTopAppBar(
+ title: String,
+ actions: @Composable RowScope.() -> Unit,
+ scrollBehavior: TopAppBarScrollBehavior,
+ searchQuery: TextFieldValue?,
+ onSearchQueryChange: (TextFieldValue?) -> Unit,
+) {
+ if (searchQuery != null) {
+ SearchTopAppBar(
+ query = searchQuery,
+ onQueryChange = onSearchQueryChange,
+ onClose = { onSearchQueryChange(null) },
+ actions = actions,
+ )
+ } else {
+ SettingsTopAppBar(title, scrollBehavior) {
+ SearchAction {
+ scrollBehavior.collapse()
+ onSearchQueryChange(TextFieldValue())
+ }
+ actions()
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun SearchTopAppBar(
+ query: TextFieldValue,
+ onQueryChange: (TextFieldValue) -> Unit,
+ onClose: () -> Unit,
+ actions: @Composable RowScope.() -> Unit = {},
+) {
+ Surface(color = SettingsTheme.colorScheme.surfaceHeader) {
+ TopAppBar(
+ title = { SearchBox(query, onQueryChange) },
+ modifier = Modifier.statusBarsPadding(),
+ navigationIcon = { CollapseAction(onClose) },
+ actions = {
+ if (query.text.isNotEmpty()) {
+ ClearAction { onQueryChange(TextFieldValue()) }
+ }
+ actions()
+ },
+ colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.Transparent),
+ )
+ }
+ BackHandler { onClose() }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit) {
+ val focusRequester = remember { FocusRequester() }
+ val textStyle = MaterialTheme.typography.bodyLarge
+ TextField(
+ value = query,
+ onValueChange = onQueryChange,
+ modifier = Modifier
+ .fillMaxWidth()
+ .focusRequester(focusRequester),
+ textStyle = textStyle,
+ placeholder = {
+ Text(
+ text = stringResource(R.string.abc_search_hint),
+ modifier = Modifier.alpha(SettingsOpacity.Hint),
+ style = textStyle,
+ )
+ },
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
+ keyboardActions = KeyboardActions(onSearch = hideKeyboardAction()),
+ singleLine = true,
+ colors = TextFieldDefaults.textFieldColors(
+ containerColor = Color.Transparent,
+ focusedIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent,
+ ),
+ )
+
+ LaunchedEffect(focusRequester) {
+ focusRequester.requestFocus()
+ }
+}
+
+@Preview
+@Composable
+private fun SearchTopAppBarPreview() {
+ SettingsTheme {
+ SearchTopAppBar(query = TextFieldValue(), onQueryChange = {}, onClose = {}) {}
+ }
+}
+
+@Preview
+@Composable
+private fun SearchScaffoldPreview() {
+ SettingsTheme {
+ SearchScaffold(title = "App notifications") { _, _ ->
+ Column {
+ Preference(object : PreferenceModel {
+ override val title = "Item 1"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Item 2"
+ })
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index d17e464..f4e504a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -16,20 +16,23 @@
package com.android.settingslib.spa.widget.scaffold
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
-import androidx.compose.material3.SmallTopAppBar
-import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.compose.horizontalValues
+import com.android.settingslib.spa.framework.compose.verticalValues
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
/**
* A [Scaffold] which content is can be full screen when needed.
@@ -41,37 +44,30 @@
actions: @Composable RowScope.() -> Unit = {},
content: @Composable (PaddingValues) -> Unit,
) {
+ val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
- topBar = {
- SmallTopAppBar(
- title = {
- Text(
- text = title,
- modifier = Modifier.padding(SettingsDimension.itemPaddingAround),
- overflow = TextOverflow.Ellipsis,
- maxLines = 1,
- )
- },
- navigationIcon = { NavigateBack() },
- actions = actions,
- colors = settingsTopAppBarColors(),
- )
- },
- content = content,
- )
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ topBar = { SettingsTopAppBar(title, scrollBehavior, actions) },
+ ) { paddingValues ->
+ Box(Modifier.padding(paddingValues.horizontalValues())) {
+ content(paddingValues.verticalValues())
+ }
+ }
}
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-internal fun settingsTopAppBarColors() = TopAppBarDefaults.largeTopAppBarColors(
- containerColor = SettingsTheme.colorScheme.surfaceHeader,
- scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
-)
-
@Preview
@Composable
private fun SettingsScaffoldPreview() {
SettingsTheme {
- SettingsScaffold(title = "Display") {}
+ SettingsScaffold(title = "Display") { paddingValues ->
+ Column(Modifier.padding(paddingValues)) {
+ Preference(object : PreferenceModel {
+ override val title = "Item 1"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Item 2"
+ })
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
new file mode 100644
index 0000000..f7cb035
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LargeTopAppBar
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
+import com.android.settingslib.spa.framework.compose.horizontalValues
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.rememberSettingsTypography
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun SettingsTopAppBar(
+ title: String,
+ scrollBehavior: TopAppBarScrollBehavior,
+ actions: @Composable RowScope.() -> Unit,
+) {
+ val colorScheme = MaterialTheme.colorScheme
+ // TODO: Remove MaterialTheme() after top app bar color fixed in AndroidX.
+ MaterialTheme(
+ colorScheme = remember { colorScheme.copy(surface = colorScheme.background) },
+ typography = rememberSettingsTypography(),
+ ) {
+ LargeTopAppBar(
+ title = { Title(title) },
+ navigationIcon = { NavigateBack() },
+ actions = actions,
+ colors = largeTopAppBarColors(),
+ scrollBehavior = scrollBehavior,
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+internal fun TopAppBarScrollBehavior.collapse() {
+ with(state) {
+ heightOffset = heightOffsetLimit
+ }
+}
+
+@Composable
+private fun Title(title: String) {
+ Text(
+ text = title,
+ modifier = Modifier
+ .padding(WindowInsets.navigationBars.asPaddingValues().horizontalValues())
+ .padding(SettingsDimension.itemPaddingAround),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun largeTopAppBarColors() = TopAppBarDefaults.largeTopAppBarColors(
+ containerColor = MaterialTheme.colorScheme.background,
+ scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
+)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
index b969076..9831b91 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
@@ -16,30 +16,26 @@
package com.android.settingslib.spa.widget.ui
-import androidx.compose.material3.Checkbox
-import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsSwitch(
checked: State<Boolean?>,
changeable: State<Boolean>,
onCheckedChange: ((newChecked: Boolean) -> Unit)? = null,
) {
- // TODO: Replace Checkbox with Switch when the androidx.compose.material3_material3 library is
- // updated to date.
val checkedValue = checked.value
if (checkedValue != null) {
- Checkbox(
+ Switch(
checked = checkedValue,
onCheckedChange = wrapOnSwitchWithLog(onCheckedChange),
enabled = changeable.value,
)
} else {
- Checkbox(
+ Switch(
checked = false,
onCheckedChange = null,
enabled = false,
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index b43bf18..4b4c6a3 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -55,11 +55,6 @@
composeOptions {
kotlinCompilerExtensionVersion jetpack_compose_compiler_version
}
- packagingOptions {
- resources {
- excludes += '/META-INF/{AL2.0,LGPL2.1}'
- }
- }
}
dependencies {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OverridableFlowTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OverridableFlowTest.kt
new file mode 100644
index 0000000..c94572b
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OverridableFlowTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.compose
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.withTimeout
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class OverridableFlowTest {
+
+ @Test
+ fun noOverride() = runTest {
+ val overridableFlow = OverridableFlow(flowOf(true))
+
+ launch {
+ val values = collectValues(overridableFlow.flow)
+ assertThat(values).containsExactly(true)
+ }
+ }
+
+ @Test
+ fun whenOverride() = runTest {
+ val overridableFlow = OverridableFlow(flowOf(true))
+
+ overridableFlow.override(false)
+
+ launch {
+ val values = collectValues(overridableFlow.flow)
+ assertThat(values).containsExactly(true, false).inOrder()
+ }
+ }
+
+ private suspend fun <T> collectValues(flow: Flow<T>): List<T> = withTimeout(500) {
+ val flowValues = mutableListOf<T>()
+ flow.toList(flowValues)
+ flowValues
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt
new file mode 100644
index 0000000..1964c43
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.material3.Text
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RegularScaffoldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun regularScaffold_titleIsDisplayed() {
+ composeTestRule.setContent {
+ RegularScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun regularScaffold_itemsAreDisplayed() {
+ composeTestRule.setContent {
+ RegularScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+ composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "title"
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
new file mode 100644
index 0000000..c3e1d54
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.scaffold
+
+import android.content.Context
+import androidx.appcompat.R
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.State
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTextInput
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SearchScaffoldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun initialState_titleIsDisplayed() {
+ composeTestRule.setContent {
+ SearchScaffold(title = TITLE) { _, _ -> }
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun initialState_clearButtonNotExist() {
+ setContent()
+
+ onClearButton().assertDoesNotExist()
+ }
+
+ @Test
+ fun initialState_searchQueryIsEmpty() {
+ val searchQuery = setContent()
+
+ assertThat(searchQuery.value).isEqualTo("")
+ }
+
+ @Test
+ fun canEnterSearchMode() {
+ val searchQuery = setContent()
+
+ clickSearchButton()
+
+ composeTestRule.onNodeWithText(TITLE).assertDoesNotExist()
+ onSearchHint().assertIsDisplayed()
+ onClearButton().assertDoesNotExist()
+ assertThat(searchQuery.value).isEqualTo("")
+ }
+
+ @Test
+ fun canExitSearchMode() {
+ val searchQuery = setContent()
+
+ clickSearchButton()
+ composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.abc_toolbar_collapse_description)
+ ).performClick()
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ onSearchHint().assertDoesNotExist()
+ onClearButton().assertDoesNotExist()
+ assertThat(searchQuery.value).isEqualTo("")
+ }
+
+ @Test
+ fun canEnterSearchQuery() {
+ val searchQuery = setContent()
+
+ clickSearchButton()
+ onSearchHint().performTextInput(QUERY)
+
+ onClearButton().assertIsDisplayed()
+ assertThat(searchQuery.value).isEqualTo(QUERY)
+ }
+
+ @Test
+ fun canClearSearchQuery() {
+ val searchQuery = setContent()
+
+ clickSearchButton()
+ onSearchHint().performTextInput(QUERY)
+ onClearButton().performClick()
+
+ onClearButton().assertDoesNotExist()
+ assertThat(searchQuery.value).isEqualTo("")
+ }
+
+ private fun setContent(): State<String> {
+ lateinit var actualSearchQuery: State<String>
+ composeTestRule.setContent {
+ SearchScaffold(title = TITLE) { _, searchQuery ->
+ SideEffect {
+ actualSearchQuery = searchQuery
+ }
+ }
+ }
+ return actualSearchQuery
+ }
+
+ private fun clickSearchButton() {
+ composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.search_menu_title)
+ ).performClick()
+ }
+
+ private fun onSearchHint() = composeTestRule.onNodeWithText(
+ context.getString(R.string.abc_search_hint)
+ )
+
+ private fun onClearButton() = composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.abc_searchview_description_clear)
+ )
+
+ private companion object {
+ const val TITLE = "title"
+ const val QUERY = "query"
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
similarity index 89%
rename from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
rename to packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
index 0c84eac..0c745d5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.spa.widget.scaffold
-import androidx.compose.runtime.Composable
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertIsNotSelected
@@ -31,15 +30,13 @@
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
-class SettingsPagerKtTest {
+class SettingsPagerTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun twoPage_initialState() {
- composeTestRule.setContent {
- TestTwoPage()
- }
+ setTwoPagesContent()
composeTestRule.onNodeWithText("Personal").assertIsSelected()
composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
@@ -49,9 +46,7 @@
@Test
fun twoPage_afterSwitch() {
- composeTestRule.setContent {
- TestTwoPage()
- }
+ setTwoPagesContent()
composeTestRule.onNodeWithText("Work").performClick()
@@ -73,11 +68,12 @@
composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
}
-}
-@Composable
-private fun TestTwoPage() {
- SettingsPager(listOf("Personal", "Work")) {
- SettingsTitle(title = "Page $it")
+ private fun setTwoPagesContent() {
+ composeTestRule.setContent {
+ SettingsPager(listOf("Personal", "Work")) {
+ SettingsTitle(title = "Page $it")
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
new file mode 100644
index 0000000..f042404
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.Text
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsScaffoldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun settingsScaffold_titleIsDisplayed() {
+ composeTestRule.setContent {
+ SettingsScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun settingsScaffold_itemsAreDisplayed() {
+ composeTestRule.setContent {
+ SettingsScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+ composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+ }
+
+ @Test
+ fun settingsScaffold_noHorizontalPadding() {
+ lateinit var actualPaddingValues: PaddingValues
+
+ composeTestRule.setContent {
+ SettingsScaffold(title = TITLE) { paddingValues ->
+ SideEffect {
+ actualPaddingValues = paddingValues
+ }
+ }
+ }
+
+ assertThat(actualPaddingValues.calculateLeftPadding(LayoutDirection.Ltr)).isEqualTo(0.dp)
+ assertThat(actualPaddingValues.calculateLeftPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
+ assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Ltr)).isEqualTo(0.dp)
+ assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
+ }
+
+ private companion object {
+ const val TITLE = "title"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
index 9964926..fd723dd 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
@@ -1,24 +1,57 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.framework.common
+import android.app.AlarmManager
+import android.app.AppOpsManager
import android.app.admin.DevicePolicyManager
import android.app.usage.StorageStatsManager
+import android.apphibernation.AppHibernationManager
import android.content.Context
import android.content.pm.verify.domain.DomainVerificationManager
import android.os.UserHandle
import android.os.UserManager
+import android.permission.PermissionControllerManager
-/** The [UserManager] instance. */
-val Context.userManager get() = getSystemService(UserManager::class.java)!!
+/** The [AlarmManager] instance. */
+val Context.alarmManager get() = getSystemService(AlarmManager::class.java)!!
+
+/** The [AppHibernationManager] instance. */
+val Context.appHibernationManager get() = getSystemService(AppHibernationManager::class.java)!!
+
+/** The [AppOpsManager] instance. */
+val Context.appOpsManager get() = getSystemService(AppOpsManager::class.java)!!
/** The [DevicePolicyManager] instance. */
val Context.devicePolicyManager get() = getSystemService(DevicePolicyManager::class.java)!!
-/** The [StorageStatsManager] instance. */
-val Context.storageStatsManager get() = getSystemService(StorageStatsManager::class.java)!!
-
/** The [DomainVerificationManager] instance. */
val Context.domainVerificationManager
get() = getSystemService(DomainVerificationManager::class.java)!!
+/** The [PermissionControllerManager] instance. */
+val Context.permissionControllerManager
+ get() = getSystemService(PermissionControllerManager::class.java)!!
+
+/** The [StorageStatsManager] instance. */
+val Context.storageStatsManager get() = getSystemService(StorageStatsManager::class.java)!!
+
+/** The [UserManager] instance. */
+val Context.userManager get() = getSystemService(UserManager::class.java)!!
+
/** Gets a new [Context] for the given [UserHandle]. */
fun Context.asUser(userHandle: UserHandle): Context = createContextAsUser(userHandle, 0)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index c5ad181..3cd8378 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -19,18 +19,18 @@
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.Dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.settingslib.spa.framework.compose.LogCompositions
import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
+import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppListConfig
@@ -55,10 +55,11 @@
option: State<Int>,
searchQuery: State<String>,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+ bottomPadding: Dp,
) {
LogCompositions(TAG, appListConfig.userId.toString())
val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
- AppListWidget(appListData, listModel, appItem)
+ AppListWidget(appListData, listModel, appItem, bottomPadding)
}
@Composable
@@ -66,6 +67,7 @@
appListData: State<AppListData<T>?>,
listModel: AppListModel<T>,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+ bottomPadding: Dp,
) {
val timeMeasurer = rememberTimeMeasurer(TAG)
appListData.value?.let { (list, option) ->
@@ -76,8 +78,8 @@
}
LazyColumn(
modifier = Modifier.fillMaxSize(),
- state = rememberLazyListState(),
- contentPadding = PaddingValues(bottom = SettingsDimension.itemPaddingVertical),
+ state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
+ contentPadding = PaddingValues(bottom = bottomPadding),
) {
items(count = list.size, key = { option to list[it].record.app.packageName }) {
val appEntry = list[it]
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 2be1d1c..2953367 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -17,9 +17,7 @@
package com.android.settingslib.spaprivileged.template.app
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -28,9 +26,8 @@
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction
-import com.android.settingslib.spa.widget.scaffold.SettingsScaffold
+import com.android.settingslib.spa.widget.scaffold.SearchScaffold
import com.android.settingslib.spa.widget.ui.Spinner
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppListConfig
@@ -50,14 +47,12 @@
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
) {
val showSystem = rememberSaveable { mutableStateOf(false) }
- // TODO: Use SearchScaffold here.
- SettingsScaffold(
+ SearchScaffold(
title = title,
actions = {
ShowSystemAction(showSystem.value) { showSystem.value = it }
},
- ) { paddingValues ->
- Spacer(Modifier.padding(paddingValues))
+ ) { bottomPadding, searchQuery ->
WorkProfilePager(primaryUserOnly) { userInfo ->
Column(Modifier.fillMaxSize()) {
val options = remember { listModel.getSpinnerOptions() }
@@ -71,8 +66,9 @@
listModel = listModel,
showSystem = showSystem,
option = selectedOption,
- searchQuery = stateOf(""),
+ searchQuery = searchQuery,
appItem = appItem,
+ bottomPadding = bottomPadding,
)
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index eb53ea1..950ee21 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -758,23 +758,16 @@
}
public boolean isBusy() {
- for (CachedBluetoothDevice memberDevice : getMemberDevice()) {
- if (isBusyState(memberDevice)) {
- return true;
+ synchronized (mProfileLock) {
+ for (LocalBluetoothProfile profile : mProfiles) {
+ int status = getProfileConnectionState(profile);
+ if (status == BluetoothProfile.STATE_CONNECTING
+ || status == BluetoothProfile.STATE_DISCONNECTING) {
+ return true;
+ }
}
+ return getBondState() == BluetoothDevice.BOND_BONDING;
}
- return isBusyState(this);
- }
-
- private boolean isBusyState(CachedBluetoothDevice device){
- for (LocalBluetoothProfile profile : device.getProfiles()) {
- int status = device.getProfileConnectionState(profile);
- if (status == BluetoothProfile.STATE_CONNECTING
- || status == BluetoothProfile.STATE_DISCONNECTING) {
- return true;
- }
- }
- return device.getBondState() == BluetoothDevice.BOND_BONDING;
}
private boolean updateProfiles() {
@@ -920,7 +913,14 @@
@Override
public String toString() {
- return mDevice.toString();
+ return "CachedBluetoothDevice ("
+ + "anonymizedAddress="
+ + mDevice.getAnonymizedAddress()
+ + ", name="
+ + getName()
+ + ", groupId="
+ + mGroupId
+ + ")";
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 8a9f9dd..fb861da 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -231,7 +231,7 @@
if (DEBUG) {
Log.d(TAG, "Adding local Volume Control profile");
}
- mVolumeControlProfile = new VolumeControlProfile();
+ mVolumeControlProfile = new VolumeControlProfile(mContext, mDeviceManager, this);
// Note: no event handler for VCP, only for being connectable.
mProfileNameMap.put(VolumeControlProfile.NAME, mVolumeControlProfile);
}
@@ -553,6 +553,10 @@
return mCsipSetCoordinatorProfile;
}
+ public VolumeControlProfile getVolumeControlProfile() {
+ return mVolumeControlProfile;
+ }
+
/**
* Fill in a list of LocalBluetoothProfile objects that are supported by
* the local device and the remote device.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index 511df28..57867be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -16,18 +16,91 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothVolumeControl;
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.RequiresApi;
/**
* VolumeControlProfile handles Bluetooth Volume Control Controller role
*/
public class VolumeControlProfile implements LocalBluetoothProfile {
private static final String TAG = "VolumeControlProfile";
+ private static boolean DEBUG = true;
static final String NAME = "VCP";
// Order of this profile in device profiles list
- private static final int ORDINAL = 23;
+ private static final int ORDINAL = 1;
+
+ private Context mContext;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+ private final LocalBluetoothProfileManager mProfileManager;
+
+ private BluetoothVolumeControl mService;
+ private boolean mIsProfileReady;
+
+ // These callbacks run on the main thread.
+ private final class VolumeControlProfileServiceListener
+ implements BluetoothProfile.ServiceListener {
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service connected");
+ }
+ mService = (BluetoothVolumeControl) proxy;
+ // We just bound to the service, so refresh the UI for any connected
+ // VolumeControlProfile devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // we may add a new device here, but generally this should not happen
+ if (device == null) {
+ if (DEBUG) {
+ Log.d(TAG, "VolumeControlProfile found new device: " + nextDevice);
+ }
+ device = mDeviceManager.addDevice(nextDevice);
+ }
+ device.onProfileStateChanged(VolumeControlProfile.this,
+ BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+
+ mProfileManager.callServiceConnectedListeners();
+ mIsProfileReady = true;
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service disconnected");
+ }
+ mProfileManager.callServiceDisconnectedListeners();
+ mIsProfileReady = false;
+ }
+ }
+
+ VolumeControlProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+ LocalBluetoothProfileManager profileManager) {
+ mContext = context;
+ mDeviceManager = deviceManager;
+ mProfileManager = profileManager;
+
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
+ new VolumeControlProfile.VolumeControlProfileServiceListener(),
+ BluetoothProfile.VOLUME_CONTROL);
+ }
@Override
public boolean accessProfileEnabled() {
@@ -39,29 +112,70 @@
return true;
}
+ /**
+ * Get VolumeControlProfile devices matching connection states{
+ *
+ * @return Matching device list
+ * @code BluetoothProfile.STATE_CONNECTED,
+ * @code BluetoothProfile.STATE_CONNECTING,
+ * @code BluetoothProfile.STATE_DISCONNECTING}
+ */
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (mService == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return mService.getDevicesMatchingConnectionStates(
+ new int[]{BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
@Override
public int getConnectionStatus(BluetoothDevice device) {
- return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle VCP
+ if (mService == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return mService.getConnectionState(device);
}
@Override
public boolean isEnabled(BluetoothDevice device) {
- return false;
+ if (mService == null || device == null) {
+ return false;
+ }
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
}
@Override
public int getConnectionPolicy(BluetoothDevice device) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; // Settings app doesn't handle VCP
+ if (mService == null || device == null) {
+ return CONNECTION_POLICY_FORBIDDEN;
+ }
+ return mService.getConnectionPolicy(device);
}
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- return false;
+ boolean isSuccessful = false;
+ if (mService == null || device == null) {
+ return false;
+ }
+ if (DEBUG) {
+ Log.d(TAG, device.getAnonymizedAddress() + " setEnabled: " + enabled);
+ }
+ if (enabled) {
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ }
+ } else {
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ }
+
+ return isSuccessful;
}
@Override
public boolean isProfileReady() {
- return true;
+ return mIsProfileReady;
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 1606540..2614644 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -92,7 +92,8 @@
COMPLICATION_TYPE_AIR_QUALITY,
COMPLICATION_TYPE_CAST_INFO,
COMPLICATION_TYPE_HOME_CONTROLS,
- COMPLICATION_TYPE_SMARTSPACE
+ COMPLICATION_TYPE_SMARTSPACE,
+ COMPLICATION_TYPE_MEDIA_ENTRY
})
@Retention(RetentionPolicy.SOURCE)
public @interface ComplicationType {
@@ -105,6 +106,7 @@
public static final int COMPLICATION_TYPE_CAST_INFO = 5;
public static final int COMPLICATION_TYPE_HOME_CONTROLS = 6;
public static final int COMPLICATION_TYPE_SMARTSPACE = 7;
+ public static final int COMPLICATION_TYPE_MEDIA_ENTRY = 8;
private final Context mContext;
private final IDreamManager mDreamManager;
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
index 03d9f2d..30d3820 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -357,5 +357,12 @@
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*/
public static final String COLUMN_IS_DEFAULT_SUBSCRIPTION = "isDefaultSubscription";
+
+ /**
+ * The name of the active data subscription state column, see
+ * {@link SubscriptionManager#getActiveDataSubscriptionId()}.
+ */
+ public static final String COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION =
+ "isActiveDataSubscriptionId";
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
index 329bd9b..23566f7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
@@ -42,7 +42,7 @@
boolean isUsableSubscription, boolean isActiveSubscriptionId,
boolean isAvailableSubscription, boolean isDefaultVoiceSubscription,
boolean isDefaultSmsSubscription, boolean isDefaultDataSubscription,
- boolean isDefaultSubscription) {
+ boolean isDefaultSubscription, boolean isActiveDataSubscriptionId) {
this.subId = subId;
this.simSlotIndex = simSlotIndex;
this.carrierId = carrierId;
@@ -72,6 +72,7 @@
this.isDefaultSmsSubscription = isDefaultSmsSubscription;
this.isDefaultDataSubscription = isDefaultDataSubscription;
this.isDefaultSubscription = isDefaultSubscription;
+ this.isActiveDataSubscriptionId = isActiveDataSubscriptionId;
}
@PrimaryKey
@@ -165,6 +166,9 @@
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SUBSCRIPTION)
public boolean isDefaultSubscription;
+ @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION)
+ public boolean isActiveDataSubscriptionId;
+
public int getSubId() {
return Integer.valueOf(subId);
}
@@ -213,6 +217,7 @@
result = 31 * result + Boolean.hashCode(isDefaultSmsSubscription);
result = 31 * result + Boolean.hashCode(isDefaultDataSubscription);
result = 31 * result + Boolean.hashCode(isDefaultSubscription);
+ result = 31 * result + Boolean.hashCode(isActiveDataSubscriptionId);
return result;
}
@@ -254,7 +259,8 @@
&& isDefaultVoiceSubscription == info.isDefaultVoiceSubscription
&& isDefaultSmsSubscription == info.isDefaultSmsSubscription
&& isDefaultDataSubscription == info.isDefaultDataSubscription
- && isDefaultSubscription == info.isDefaultSubscription;
+ && isDefaultSubscription == info.isDefaultSubscription
+ && isActiveDataSubscriptionId == info.isActiveDataSubscriptionId;
}
public String toString() {
@@ -317,6 +323,8 @@
.append(isDefaultDataSubscription)
.append(", isDefaultSubscription = ")
.append(isDefaultSubscription)
+ .append(", isActiveDataSubscriptionId = ")
+ .append(isActiveDataSubscriptionId)
.append(")}");
return builder.toString();
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 315ab0a..79e9938 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1069,80 +1069,4 @@
assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
}
-
- @Test
- public void isBusy_mainDeviceIsConnecting_returnsBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTING);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isTrue();
- }
-
- @Test
- public void isBusy_mainDeviceIsBonding_returnsBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isTrue();
- }
-
- @Test
- public void isBusy_memberDeviceIsConnecting_returnsBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTING);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isTrue();
- }
-
- @Test
- public void isBusy_memberDeviceIsBonding_returnsBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isTrue();
- }
-
- @Test
- public void isBusy_allDevicesAreNotBusy_returnsNotBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isFalse();
- }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 808ea9e..6d375ac 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -549,7 +549,7 @@
try {
IActivityManager am = ActivityManager.getService();
- Configuration config = am.getConfiguration();
+ final Configuration config = new Configuration();
config.setLocales(merged);
// indicate this isn't some passing default - the user wants this remembered
config.userSetLocale = true;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ccbfac2..fa96a2f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -5533,13 +5533,17 @@
}
if (currentVersion == 210) {
final SettingsState secureSettings = getSecureSettingsLocked(userId);
- final int defaultValueVibrateIconEnabled = getContext().getResources()
- .getInteger(R.integer.def_statusBarVibrateIconEnabled);
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
- String.valueOf(defaultValueVibrateIconEnabled),
- null /* tag */, true /* makeDefault */,
- SettingsState.SYSTEM_PACKAGE_NAME);
+ final Setting currentSetting = secureSettings.getSettingLocked(
+ Secure.STATUS_BAR_SHOW_VIBRATE_ICON);
+ if (currentSetting.isNull()) {
+ final int defaultValueVibrateIconEnabled = getContext().getResources()
+ .getInteger(R.integer.def_statusBarVibrateIconEnabled);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
+ String.valueOf(defaultValueVibrateIconEnabled),
+ null /* tag */, true /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
currentVersion = 211;
}
// vXXX: Add new settings above this point.
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 765ee89..c388826 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -42,8 +42,6 @@
import android.util.Base64;
import android.util.Slog;
import android.util.TimeUtils;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
@@ -51,6 +49,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
@@ -1087,6 +1087,7 @@
parseStateLocked(parser);
return true;
} catch (XmlPullParserException | IOException e) {
+ Slog.e(LOG_TAG, "parse settings xml failed", e);
return false;
} finally {
IoUtils.closeQuietly(in);
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 4ed28d5..55160fb 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -17,9 +17,10 @@
import android.os.Looper;
import android.test.AndroidTestCase;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlSerializer;
+
import com.google.common.base.Strings;
import java.io.ByteArrayOutputStream;
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b5145f9..4267ba2 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -289,6 +289,12 @@
<!-- Query all packages on device on R+ -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <queries>
+ <intent>
+ <action android:name="android.intent.action.NOTES" />
+ </intent>
+ </queries>
+
<!-- Permission to register process observer -->
<uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"/>
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index 38d636d7..95b986f 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -8,7 +8,7 @@
### Step 1: create a new quick affordance config
* Create a new class under the [systemui/keyguard/domain/quickaffordance](../../src/com/android/systemui/keyguard/domain/quickaffordance) directory
* Please make sure that the class is injected through the Dagger dependency injection system by using the `@Inject` annotation on its main constructor and the `@SysUISingleton` annotation at class level, to make sure only one instance of the class is ever instantiated
-* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
+* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
* The `state` Flow property must emit `State.Hidden` when the feature is not enabled!
* It is safe to assume that `onQuickAffordanceClicked` will not be invoked if-and-only-if the previous rule is followed
* When implementing `onQuickAffordanceClicked`, the implementation can do something or it can ask the framework to start an activity using an `Intent` provided by the implementation
diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml
index d5e84f9..d5552f6 100644
--- a/packages/SystemUI/res-keyguard/values-af/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-af/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon word vereis nadat toestel herbegin het"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN word vereis nadat toestel herbegin het"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wagwoord word vereis nadat toestel herbegin het"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Patroon word vir bykomende sekuriteit vereis"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN word vir bykomende sekuriteit vereis"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Wagwoord word vir bykomende sekuriteit vereis"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Toestel is deur administrateur gesluit"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Toestel is handmatig gesluit"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nie herken nie"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Verstek"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Borrel"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml
index be52c44..533e5a2 100644
--- a/packages/SystemUI/res-keyguard/values-am/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-am/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"መሣሪያ ዳግም ከጀመረ በኋላ ሥርዓተ ጥለት ያስፈልጋል"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"መሣሪያ ዳግም ከተነሳ በኋላ ፒን ያስፈልጋል"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"መሣሪያ ዳግም ከጀመረ በኋላ የይለፍ ቃል ያስፈልጋል"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ሥርዓተ ጥለት ለተጨማሪ ደህንነት ያስፈልጋል"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ፒን ለተጨማሪ ደህንነት ያስፈልጋል"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"የይለፍ ቃል ለተጨማሪ ደህንነት ያስፈልጋል"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"መሣሪያ በአስተዳዳሪ ተቆልፏል"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"መሣሪያ በተጠቃሚው ራሱ ተቆልፏል"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"አልታወቀም"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ነባሪ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"አረፋ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"አናሎግ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index adb57b6..81ce7d3 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"يجب رسم النقش بعد إعادة تشغيل الجهاز"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"يجب إدخال رقم التعريف الشخصي بعد إعادة تشغيل الجهاز"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"يجب إدخال كلمة المرور بعد إعادة تشغيل الجهاز"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"يجب رسم النقش لمزيد من الأمان"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"يجب إدخال رقم التعريف الشخصي لمزيد من الأمان"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"يجب إدخال كلمة المرور لمزيد من الأمان"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"اختار المشرف قفل الجهاز"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"تم حظر الجهاز يدويًا"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"لم يتم التعرّف عليه."</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"تلقائي"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"فقاعة"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ساعة تقليدية"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml
index cbfb325..443f666 100644
--- a/packages/SystemUI/res-keyguard/values-as/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-as/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পিন দিয়াটো বাধ্যতামূলক"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"অতিৰিক্ত সুৰক্ষাৰ বাবে আর্হি দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"অতিৰিক্ত সুৰক্ষাৰ বাবে পিন দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"অতিৰিক্ত সুৰক্ষাৰ বাবে পাছৱর্ড দিয়াটো বাধ্যতামূলক"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"প্ৰশাসকে ডিভাইচ লক কৰি ৰাখিছে"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ডিভাইচটো মেনুৱেলভাৱে লক কৰা হৈছিল"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"চিনাক্ত কৰিব পৰা নাই"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ডিফ’ল্ট"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"এনাল’গ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml
index 6ec1061..e125697 100644
--- a/packages/SystemUI/res-keyguard/values-az/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-az/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yenidən başladıqdan sonra model tələb olunur"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıqdan sonra PIN tələb olunur"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıqdan sonra parol tələb olunur"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Əlavə təhlükəsizlik üçün model tələb olunur"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Əlavə təhlükəsizlik üçün PIN tələb olunur"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Əlavə təhlükəsizlik üçün parol tələb olunur"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Cihaz admin tərəfindən kilidlənib"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Cihaz əl ilə kilidləndi"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tanınmır"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Defolt"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Qabarcıq"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoq"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
index 13d6613..f0d1ef2 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Treba da unesete šablon kada se uređaj ponovo pokrene"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Treba da unesete PIN kada se uređaj ponovo pokrene"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Treba da unesete lozinku kada se uređaj ponovo pokrene"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Treba da unesete šablon radi dodatne bezbednosti"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Treba da unesete PIN radi dodatne bezbednosti"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Treba da unesete lozinku radi dodatne bezbednosti"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrator je zaključao uređaj"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznat"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Podrazumevani"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mehurići"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml
index 616d31a..e1af3ece 100644
--- a/packages/SystemUI/res-keyguard/values-be/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-be/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Пасля перазапуску прылады патрабуецца ўзор"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Пасля перазапуску прылады патрабуецца PIN-код"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Пасля перазапуску прылады патрабуецца пароль"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Для забеспячэння дадатковай бяспекі патрабуецца ўзор"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Для забеспячэння дадатковай бяспекі патрабуецца PIN-код"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Для забеспячэння дадатковай бяспекі патрабуецца пароль"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Прылада заблакіравана адміністратарам"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Прылада была заблакіравана ўручную"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Не распазнана"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Стандартны"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Бурбалкі"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Са стрэлкамі"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml
index 366a7f4..0b4417a 100644
--- a/packages/SystemUI/res-keyguard/values-bg/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"След рестартиране на устройството се изисква фигура"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"След рестартиране на устройството се изисква ПИН код"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"След рестартиране на устройството се изисква парола"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"За допълнителна сигурност се изисква фигура"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"За допълнителна сигурност се изисква ПИН код"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"За допълнителна сигурност се изисква парола"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Устройството е заключено от администратора"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Устройството бе заключено ръчно"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Не е разпознато"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Стандартен"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Балонен"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналогов"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index c20be5d..4851579 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইসটি পুনরায় চালু হওয়ার পর প্যাটার্নের প্রয়োজন হবে"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইসটি পুনরায় চালু হওয়ার পর পিন প্রয়োজন হবে"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইসটি পুনরায় চালু হওয়ার পর পাসওয়ার্ডের প্রয়োজন হবে"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"অতিরিক্ত সুরক্ষার জন্য প্যাটার্ন দেওয়া প্রয়োজন"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"অতিরিক্ত সুরক্ষার জন্য পিন দেওয়া প্রয়োজন"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"অতিরিক্ত সুরক্ষার জন্য পাসওয়ার্ড দেওয়া প্রয়োজন"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"প্রশাসক ডিভাইসটি লক করেছেন"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ডিভাইসটিকে ম্যানুয়ালি লক করা হয়েছে"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"শনাক্ত করা যায়নি"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ডিফল্ট"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"অ্যানালগ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index f1c00a9..4705b4d9 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Potreban je uzorak nakon što se uređaj ponovo pokrene"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Potreban je PIN nakon što se uređaj ponovo pokrene"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Potrebna je lozinka nakon što se uređaj ponovo pokrene"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Uzorak je potreban radi dodatne sigurnosti"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN je potreban radi dodatne sigurnosti"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Lozinka je potrebna radi dodatne sigurnosti"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Uređaj je zaključao administrator"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznato"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Zadano"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mjehurići"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index 709407c..284eaeb 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cal introduir el patró quan es reinicia el dispositiu"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cal introduir el PIN quan es reinicia el dispositiu"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cal introduir la contrasenya quan es reinicia el dispositiu"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Cal introduir el patró per disposar de més seguretat"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Cal introduir el PIN per disposar de més seguretat"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Cal introduir la contrasenya per disposar de més seguretat"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"L\'administrador ha bloquejat el dispositiu"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositiu s\'ha bloquejat manualment"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"No s\'ha reconegut"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminada"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bombolla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analògica"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index a44658c..6b4f607 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po restartování zařízení je vyžadováno gesto"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po restartování zařízení je vyžadován kód PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po restartování zařízení je vyžadováno heslo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pro ještě lepší zabezpečení je vyžadováno gesto"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Pro ještě lepší zabezpečení je vyžadován kód PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Pro ještě lepší zabezpečení je vyžadováno heslo"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Zařízení je uzamknuto administrátorem"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Zařízení bylo ručně uzamčeno"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nerozpoznáno"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Výchozí"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogové"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml
index 331c355..85238df 100644
--- a/packages/SystemUI/res-keyguard/values-da/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-da/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du skal angive et mønster, når du har genstartet enheden"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Der skal angives en pinkode efter genstart af enheden"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Der skal angives en adgangskode efter genstart af enheden"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Der kræves et mønster som ekstra beskyttelse"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Der kræves en pinkode som ekstra beskyttelse"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Der kræves en adgangskode som ekstra beskyttelse"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Enheden er blevet låst af administratoren"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheden blev låst manuelt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ikke genkendt"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Boble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml
index c19b357..18befed 100644
--- a/packages/SystemUI/res-keyguard/values-de/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-de/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nach dem Neustart des Geräts ist die Eingabe des Musters erforderlich"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nach dem Neustart des Geräts ist die Eingabe der PIN erforderlich"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nach dem Neustart des Geräts ist die Eingabe des Passworts erforderlich"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Zur Verbesserung der Sicherheit ist ein Muster erforderlich"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Zur Verbesserung der Sicherheit ist eine PIN erforderlich"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Zur Verbesserung der Sicherheit ist ein Passwort erforderlich"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Gerät vom Administrator gesperrt"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Gerät manuell gesperrt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nicht erkannt"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml
index 1d6ec82..65b84486 100644
--- a/packages/SystemUI/res-keyguard/values-el/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-el/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Απαιτείται μοτίβο μετά από την επανεκκίνηση της συσκευής"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Απαιτείται PIN μετά από την επανεκκίνηση της συσκευής"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Απαιτείται κωδικός πρόσβασης μετά από την επανεκκίνηση της συσκευής"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Απαιτείται μοτίβο για πρόσθετη ασφάλεια"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Απαιτείται PIN για πρόσθετη ασφάλεια"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Απαιτείται κωδικός πρόσβασης για πρόσθετη ασφάλεια"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Η συσκευή κλειδώθηκε από τον διαχειριστή"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Η συσκευή κλειδώθηκε με μη αυτόματο τρόπο"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Δεν αναγνωρίστηκε"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Προεπιλογή"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Συννεφάκι"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Αναλογικό"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
index 2b78f96..588f1b5 100644
--- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index e1c2532..08fc8d6 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
index 2b78f96..588f1b5 100644
--- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 2b78f96..588f1b5 100644
--- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
index 9052e4f..a23aeb0 100644
--- a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"For additional security, use pattern instead"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"For additional security, use PIN instead"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognized"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
index 9dc054a..c71a678 100644
--- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Se requiere el patrón después de reiniciar el dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Se requiere el PIN después de reiniciar el dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Se requiere la contraseña después de reiniciar el dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Se requiere el patrón por razones de seguridad"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Se requiere el PIN por razones de seguridad"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Se requiere la contraseña por razones de seguridad"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado por el administrador"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositivo se bloqueó de forma manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"No se reconoció"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Burbuja"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml
index f9f0452..c6ee698 100644
--- a/packages/SystemUI/res-keyguard/values-es/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Debes introducir el patrón después de reiniciar el dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Debes introducir el PIN después de reiniciar el dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Debes introducir la contraseña después de reiniciar el dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Debes introducir el patrón como medida de seguridad adicional"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Debes introducir el PIN como medida de seguridad adicional"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Debes introducir la contraseña como medida de seguridad adicional"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado por el administrador"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositivo se ha bloqueado manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"No se reconoce"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Burbuja"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index dceb78e..071ede8 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pärast seadme taaskäivitamist tuleb sisestada muster"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pärast seadme taaskäivitamist tuleb sisestada PIN-kood"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pärast seadme taaskäivitamist tuleb sisestada parool"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Lisaturvalisuse huvides tuleb sisestada muster"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Lisaturvalisuse huvides tuleb sisestada PIN-kood"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Lisaturvalisuse huvides tuleb sisestada parool"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administraator lukustas seadme"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Seade lukustati käsitsi"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ei tuvastatud"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Vaikenumbrilaud"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mull"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index 8431268..9b8e65b 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Eredua marraztu beharko duzu gailua berrabiarazten denean"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PINa idatzi beharko duzu gailua berrabiarazten denean"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pasahitza idatzi beharko duzu gailua berrabiarazten denean"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Eredua behar da gailua babestuago izateko"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PINa behar da gailua babestuago izateko"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Pasahitza behar da gailua babestuago izateko"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratzaileak blokeatu egin du gailua"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Eskuz blokeatu da gailua"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ez da ezagutu"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Lehenetsia"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Puxikak"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogikoa"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index 37bb260..3583f1e 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"بعد از بازنشانی دستگاه باید الگو وارد شود"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"بعد از بازنشانی دستگاه باید پین وارد شود"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"بعد از بازنشانی دستگاه باید گذرواژه وارد شود"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"برای ایمنی بیشتر باید الگو وارد شود"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"برای ایمنی بیشتر باید پین وارد شود"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"برای ایمنی بیشتر باید گذرواژه وارد شود"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"دستگاه توسط سرپرست سیستم قفل شده است"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"دستگاه بهصورت دستی قفل شده است"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"شناسایی نشد"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"پیشفرض"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"حباب"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"آنالوگ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml
index f8cec42..a0ac6df 100644
--- a/packages/SystemUI/res-keyguard/values-fi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kuvio vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-koodi vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Salasana vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kuvio vaaditaan suojauksen parantamiseksi."</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN-koodi vaaditaan suojauksen parantamiseksi."</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Salasana vaaditaan suojauksen parantamiseksi."</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Järjestelmänvalvoja lukitsi laitteen."</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Laite lukittiin manuaalisesti"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ei tunnistettu"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Oletus"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Kupla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoginen"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
index 077fe11..66fd7c0 100644
--- a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Le schéma est exigé après le redémarrage de l\'appareil"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Le NIP est exigé après le redémarrage de l\'appareil"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Le mot de passe est exigé après le redémarrage de l\'appareil"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Le schéma est exigé pour plus de sécurité"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Le NIP est exigé pour plus de sécurité"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Le mot de passe est exigé pour plus de sécurité"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Pour plus de sécurité, utilisez plutôt un schéma"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Pour plus de sécurité, utilisez plutôt un NIP"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Pour plus de sécurité, utilisez plutôt un mot de passe"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"L\'appareil a été verrouillé par l\'administrateur"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"L\'appareil a été verrouillé manuellement"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Doigt non reconnu"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Par défaut"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bulle"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogique"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Déverrouiller votre appareil pour continuer"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index 45dadc1..ec00ba3 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Veuillez dessiner le schéma après le redémarrage de l\'appareil"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Veuillez saisir le code après le redémarrage de l\'appareil"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Veuillez saisir le mot de passe après le redémarrage de l\'appareil"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Veuillez dessiner le schéma pour renforcer la sécurité"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Veuillez saisir le code pour renforcer la sécurité"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Veuillez saisir le mot de passe pour renforcer la sécurité"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Appareil verrouillé par l\'administrateur"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Appareil verrouillé manuellement"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Non reconnu"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Par défaut"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bulle"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogique"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index 4fbdd67..a3f8e86 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"É necesario o padrón despois do reinicio do dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"É necesario o PIN despois do reinicio do dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"É necesario o contrasinal despois do reinicio do dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"É necesario o padrón para obter seguranza adicional"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"É necesario o PIN para obter seguranza adicional"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"É necesario o contrasinal para obter seguranza adicional"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"O administrador bloqueou o dispositivo"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo bloqueouse manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Non se recoñeceu"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Burbulla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analóxico"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index 6caac8a..c97fe01 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પૅટર્ન જરૂરી છે"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પિન જરૂરી છે"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પાસવર્ડ જરૂરી છે"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"વધારાની સુરક્ષા માટે પૅટર્ન જરૂરી છે"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"વધારાની સુરક્ષા માટે પિન જરૂરી છે"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"વધારાની સુરક્ષા માટે પાસવર્ડ જરૂરી છે"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"વ્યવસ્થાપકે ઉપકરણ લૉક કરેલું છે"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ઉપકરણ મેન્યુઅલી લૉક કર્યું હતું"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ઓળખાયેલ નથી"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ડિફૉલ્ટ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"બબલ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"એનાલોગ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 627576e..1283004 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिवाइस फिर से चालू होने के बाद पैटर्न ज़रूरी है"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिवाइस फिर से चालू होने के बाद पिन ज़रूरी है"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिवाइस फिर से चालू होने के बाद पासवर्ड ज़रूरी है"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"अतिरिक्त सुरक्षा के लिए पैटर्न ज़रूरी है"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"अतिरिक्त सुरक्षा के लिए पिन ज़रूरी है"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"अतिरिक्त सुरक्षा के लिए पासवर्ड ज़रूरी है"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"व्यवस्थापक ने डिवाइस को लॉक किया है"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"डिवाइस को मैन्युअल रूप से लॉक किया गया था"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"पहचान नहीं हो पाई"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"डिफ़ॉल्ट"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"एनालॉग"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml
index 8b1b504..7a14a80 100644
--- a/packages/SystemUI/res-keyguard/values-hr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nakon ponovnog pokretanja uređaja morate unijeti uzorak"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nakon ponovnog pokretanja uređaja morate unijeti PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nakon ponovnog pokretanja uređaja morate unijeti zaporku"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Unesite uzorak radi dodatne sigurnosti"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Unesite PIN radi dodatne sigurnosti"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Unesite zaporku radi dodatne sigurnosti"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrator je zaključao uređaj"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznato"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Zadano"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mjehurić"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml
index 6b75e72..a4fbf53 100644
--- a/packages/SystemUI/res-keyguard/values-hu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Az eszköz újraindítását követően meg kell adni a mintát"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Az eszköz újraindítását követően meg kell adni a PIN-kódot"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Az eszköz újraindítását követően meg kell adni a jelszót"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"A nagyobb biztonság érdekében minta szükséges"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"A nagyobb biztonság érdekében PIN-kód szükséges"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"A nagyobb biztonság érdekében jelszó szükséges"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"A rendszergazda zárolta az eszközt"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Az eszközt manuálisan lezárták"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nem ismerhető fel"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Alapértelmezett"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Buborék"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analóg"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml
index 3412026..086eeb9 100644
--- a/packages/SystemUI/res-keyguard/values-hy/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել նախշը"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել PIN կոդը"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել գաղտնաբառը"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Անվտանգության նկատառումներից ելնելով անհրաժեշտ է մուտքագրել նախշը"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Անվտանգության նկատառումներից ելնելով անհրաժեշտ է մուտքագրել PIN կոդը"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Անվտանգության նկատառումներից ելնելով անհրաժեշտ է մուտքագրել գաղտնաբառը"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Սարքը կողպված է ադմինիստրատորի կողմից"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Սարքը կողպվել է ձեռքով"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Չհաջողվեց ճանաչել"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Կանխադրված"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Պղպջակ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Անալոգային"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index 1afb791..b43a032 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pola diperlukan setelah perangkat dimulai ulang"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah perangkat dimulai ulang"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Sandi diperlukan setelah perangkat dimulai ulang"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pola diperlukan untuk keamanan tambahan"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN diperlukan untuk keamanan tambahan"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Sandi diperlukan untuk keamanan tambahan"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Perangkat dikunci oleh admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Perangkat dikunci secara manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml
index 6abdc82..8bad961 100644
--- a/packages/SystemUI/res-keyguard/values-is/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-is/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Mynsturs er krafist þegar tækið er endurræst"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-númers er krafist þegar tækið er endurræst"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Aðgangsorðs er krafist þegar tækið er endurræst"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Mynsturs er krafist af öryggisástæðum"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN-númers er krafist af öryggisástæðum"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Aðgangsorðs er krafist af öryggisástæðum"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Kerfisstjóri læsti tæki"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Tækinu var læst handvirkt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Þekktist ekki"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Sjálfgefið"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Blaðra"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Með vísum"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml
index 9fed5f7..186177ff 100644
--- a/packages/SystemUI/res-keyguard/values-it/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-it/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Sequenza obbligatoria dopo il riavvio del dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN obbligatorio dopo il riavvio del dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password obbligatoria dopo il riavvio del dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Sequenza obbligatoria per maggiore sicurezza"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN obbligatorio per maggiore sicurezza"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password obbligatoria per maggiore sicurezza"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloccato dall\'amministratore"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Il dispositivo è stato bloccato manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Non riconosciuto"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predefinito"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bolla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogico"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index b5b1c53..aab4206 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"יש להזין את קו ביטול הנעילה לאחר הפעלה מחדש של המכשיר"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"צריך להזין קוד אימות לאחר הפעלה מחדש של המכשיר"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"יש להזין סיסמה לאחר הפעלה מחדש של המכשיר"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"יש להזין את קו ביטול הנעילה כדי להגביר את רמת האבטחה"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"יש להזין קוד אימות כדי להגביר את רמת האבטחה"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"יש להזין סיסמה כדי להגביר את רמת האבטחה"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"כדי להגביר את רמת האבטחה, כדאי להשתמש בקו ביטול נעילה במקום זאת"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"כדי להגביר את רמת האבטחה, כדאי להשתמש בקוד אימות במקום זאת"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"כדי להגביר את רמת האבטחה, כדאי להשתמש בסיסמה במקום זאת"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"המנהל של המכשיר נהל אותו"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"המכשיר ננעל באופן ידני"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"לא זוהתה"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"ברירת מחדל"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"בועה"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"אנלוגי"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"צריך לבטל את הנעילה של המכשיר כדי להמשיך"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml
index afe0159..1a4fb0b 100644
--- a/packages/SystemUI/res-keyguard/values-ja/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ja/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"デバイスの再起動後はパターンの入力が必要となります"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"デバイスの再起動後は PIN の入力が必要となります"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"デバイスの再起動後はパスワードの入力が必要となります"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"追加の確認のためパターンが必要です"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"追加の確認のため PIN が必要です"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"追加の確認のためパスワードが必要です"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"セキュリティを強化するには代わりにパターンを使用してください"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"セキュリティを強化するには代わりに PIN を使用してください"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"セキュリティを強化するには代わりにパスワードを使用してください"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"デバイスは管理者によりロックされています"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"デバイスは手動でロックされました"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"認識されませんでした"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"デフォルト"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"バブル"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"アナログ"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"続行するにはデバイスのロックを解除してください"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml
index b32caa7..123cc39 100644
--- a/packages/SystemUI/res-keyguard/values-ka/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა ნიმუშის დახატვა"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა PIN-კოდის შეყვანა"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა პაროლის შეყვანა"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"დამატებითი უსაფრთხოებისთვის საჭიროა ნიმუშის დახატვა"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"დამატებითი უსაფრთხოებისთვის საჭიროა PIN-კოდის შეყვანა"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"დამატებითი უსაფრთხოებისთვის საჭიროა პაროლის შეყვანა"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"მოწყობილობა ჩაკეტილია ადმინისტრატორის მიერ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"მოწყობილობა ხელით ჩაიკეტა"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"არ არის ამოცნობილი"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ნაგულისხმევი"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ბუშტი"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ანალოგური"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml
index d6d5bcd..8daca5c 100644
--- a/packages/SystemUI/res-keyguard/values-kk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Құрылғы қайта іске қосылғаннан кейін, өрнекті енгізу қажет"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Құрылғы қайта іске қосылғаннан кейін, PIN кодын енгізу қажет"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Құрылғы қайта іске қосылғаннан кейін, құпия сөзді енгізу қажет"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Қауіпсіздікті күшейту үшін өрнекті енгізу қажет"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Қауіпсіздікті күшейту үшін PIN кодын енгізу қажет"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Қауіпсіздікті күшейту үшін құпия сөзді енгізу қажет"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Құрылғыны әкімші құлыптаған"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Құрылғы қолмен құлыпталды"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Танылмады"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Әдепкі"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Көпіршік"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналогтық"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index 00bfe05..73f507c 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"តម្រូវឲ្យប្រើលំនាំ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"តម្រូវឲ្យបញ្ចូលកូដ PIN បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"តម្រូវឲ្យបញ្ចូលពាក្យសម្ងាត់ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"តម្រូវឲ្យប្រើលំនាំ ដើម្បីទទួលបានសវុត្ថិភាពបន្ថែម"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"តម្រូវឲ្យបញ្ចូលកូដ PIN ដើម្បីទទួលបានសុវត្ថិភាពបន្ថែម"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"តម្រូវឲ្យបញ្ចូលពាក្យសម្ងាត់ ដើម្បីទទួលបានសុវត្ថិភាពបន្ថែម"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ឧបករណ៍ត្រូវបានចាក់សោដោយអ្នកគ្រប់គ្រង"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ឧបករណ៍ត្រូវបានចាក់សោដោយអ្នកប្រើផ្ទាល់"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"មិនអាចសម្គាល់បានទេ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"លំនាំដើម"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ពពុះ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"អាណាឡូក"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index 80a98e6..c279cea 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪ್ಯಾಟರ್ನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಿನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಾಸ್ವರ್ಡ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗೆ ಪ್ಯಾಟರ್ನ್ ಅಗತ್ಯವಿದೆ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗೆ ಪಿನ್ ಅಗತ್ಯವಿದೆ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗಾಗಿ ಪಾಸ್ವರ್ಡ್ ಅಗತ್ಯವಿದೆ"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ನಿರ್ವಾಹಕರು ಸಾಧನವನ್ನು ಲಾಕ್ ಮಾಡಿದ್ದಾರೆ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ಸಾಧನವನ್ನು ಹಸ್ತಚಾಲಿತವಾಗಿ ಲಾಕ್ ಮಾಡಲಾಗಿದೆ"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ಡೀಫಾಲ್ಟ್"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ಬಬಲ್"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ಅನಲಾಗ್"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index b952f1b..4c058ed 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"기기가 다시 시작되면 패턴이 필요합니다."</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"기기가 다시 시작되면 PIN이 필요합니다."</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"기기가 다시 시작되면 비밀번호가 필요합니다."</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"보안 강화를 위해 패턴이 필요합니다."</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"보안 강화를 위해 PIN이 필요합니다."</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"보안 강화를 위해 비밀번호가 필요합니다."</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"관리자가 기기를 잠갔습니다."</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"기기가 수동으로 잠금 설정되었습니다."</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"인식할 수 없음"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"기본"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"버블"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"아날로그"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 485337d..7c7099e 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Түзмөк кайра күйгүзүлгөндөн кийин графикалык ачкычты тартуу талап кылынат"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Түзмөк кайра күйгүзүлгөндөн кийин PIN-кодду киргизүү талап кылынат"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Түзмөк кайра күйгүзүлгөндөн кийин сырсөздү киргизүү талап кылынат"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Коопсуздукту бекемдөө үчүн графикалык ачкыч талап кылынат"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Коопсуздукту бекемдөө үчүн PIN-код талап кылынат"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Коопсуздукту бекемдөө үчүн сырсөз талап кылынат"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Түзмөктү администратор кулпулап койгон"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Түзмөк кол менен кулпуланды"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Таанылган жок"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Демейки"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Көбүк"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналог"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-lo/strings.xml b/packages/SystemUI/res-keyguard/values-lo/strings.xml
index 17584b5..5a6df42 100644
--- a/packages/SystemUI/res-keyguard/values-lo/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lo/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ຈຳເປັນຕ້ອງມີແບບຮູບປົດລັອກຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ຈຳເປັນຕ້ອງມີ PIN ຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ຈຳເປັນຕ້ອງມີລະຫັດຜ່ານຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ຈຳເປັນຕ້ອງມີແບບຮູບເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ຈຳເປັນຕ້ອງມີ PIN ເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ຈຳເປັນຕ້ອງມີລະຫັດຜ່ານເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ອຸປະກອນຖືກລັອກໂດຍຜູ້ເບິ່ງແຍງລະບົບ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ອຸປະກອນຖືກສັ່ງໃຫ້ລັອກ"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ບໍ່ຮູ້ຈັກ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ຄ່າເລີ່ມຕົ້ນ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ຟອງ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ໂມງເຂັມ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml
index a066a66..4d98fd1 100644
--- a/packages/SystemUI/res-keyguard/values-lt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iš naujo paleidus įrenginį būtinas atrakinimo piešinys"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iš naujo paleidus įrenginį būtinas PIN kodas"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iš naujo paleidus įrenginį būtinas slaptažodis"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Norint užtikrinti papildomą saugą būtinas atrakinimo piešinys"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Norint užtikrinti papildomą saugą būtinas PIN kodas"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Norint užtikrinti papildomą saugą būtinas slaptažodis"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Įrenginį užrakino administratorius"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Įrenginys užrakintas neautomatiškai"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Neatpažinta"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Numatytasis"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Debesėlis"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoginis"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml
index d371a4b..2660a06 100644
--- a/packages/SystemUI/res-keyguard/values-lv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pēc ierīces restartēšanas ir jāievada atbloķēšanas kombinācija."</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pēc ierīces restartēšanas ir jāievada PIN kods."</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pēc ierīces restartēšanas ir jāievada parole."</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Papildu drošībai ir jāievada atbloķēšanas kombinācija."</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Papildu drošībai ir jāievada PIN kods."</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Papildu drošībai ir jāievada parole."</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrators bloķēja ierīci."</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Ierīce tika bloķēta manuāli."</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nav atpazīts"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Noklusējums"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Burbuļi"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogais"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-mk/strings.xml b/packages/SystemUI/res-keyguard/values-mk/strings.xml
index ef22564..77e1b50 100644
--- a/packages/SystemUI/res-keyguard/values-mk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mk/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Потребна е шема по рестартирање на уредот"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Потребен е PIN-код по рестартирање на уредот"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Потребна е лозинка по рестартирање на уредот"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Потребна е шема за дополнителна безбедност"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Потребен е PIN-код за дополнителна безбедност"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Потребна е лозинка за дополнителна безбедност"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"За дополнителна безбедност, користете шема"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"За дополнителна безбедност, користете PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"За дополнителна безбедност, користете лозинка"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Уредот е заклучен од администраторот"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Уредот е заклучен рачно"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Непознат"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Стандарден"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Балонче"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналоген"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Отклучете го уредот за да продолжите"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index 63a542a..e62b435 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം പാറ്റേൺ വരയ്ക്കേണ്ടതുണ്ട്"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം പിൻ നൽകേണ്ടതുണ്ട്"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം പാസ്വേഡ് നൽകേണ്ടതുണ്ട്"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"അധിക സുരക്ഷയ്ക്ക് പാറ്റേൺ ആവശ്യമാണ്"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"അധിക സുരക്ഷയ്ക്ക് പിൻ ആവശ്യമാണ്"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"അധിക സുരക്ഷയ്ക്ക് പാസ്വേഡ് ആവശ്യമാണ്"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ഉപകരണം അഡ്മിൻ ലോക്കുചെയ്തു"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ഉപകരണം നേരിട്ട് ലോക്കുചെയ്തു"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"തിരിച്ചറിയുന്നില്ല"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ഡിഫോൾട്ട്"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ബബിൾ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"അനലോഗ്"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-mn/strings.xml b/packages/SystemUI/res-keyguard/values-mn/strings.xml
index 71c913f..f2cc5ab 100644
--- a/packages/SystemUI/res-keyguard/values-mn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mn/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Төхөөрөмжийг дахин эхлүүлсний дараа загвар оруулах шаардлагатай"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Төхөөрөмжийг дахин эхлүүлсний дараа ПИН оруулах шаардлагатай"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Төхөөрөмжийг дахин эхлүүлсний дараа нууц үг оруулах шаардлагатай"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Аюулгүй байдлын үүднээс загвар оруулах шаардлагатай"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Аюулгүй байдлын үүднээс ПИН оруулах шаардлагатай"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Аюулгүй байдлын үүднээс нууц үг оруулах шаардлагатай"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Нэмэлт аюулгүй байдлын үүднээс оронд нь хээ ашиглана уу"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Нэмэлт аюулгүй байдлын үүднээс оронд нь ПИН ашиглана уу"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Нэмэлт аюулгүй байдлын үүднээс оронд нь нууц үг ашиглана уу"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Админ төхөөрөмжийг түгжсэн"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Төхөөрөмжийг гараар түгжсэн"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Таньж чадсангүй"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Өгөгдмөл"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Бөмбөлөг"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Aналог"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Үргэлжлүүлэхийн тулд төхөөрөмжийнхөө түгжээг тайлна уу"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml
index 6ac13bd..1454b20 100644
--- a/packages/SystemUI/res-keyguard/values-mr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिव्हाइस रीस्टार्ट झाल्यावर पॅटर्न आवश्यक आहे"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिव्हाइस रीस्टार्ट झाल्यावर पिन आवश्यक आहे"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिव्हाइस रीस्टार्ट झाल्यावर पासवर्ड आवश्यक आहे"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"अतिरिक्त सुरक्षिततेसाठी पॅटर्न आवश्यक आहे"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"अतिरिक्त सुरक्षिततेसाठी पिन आवश्यक आहे"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"अतिरिक्त सुरक्षिततेसाठी पासवर्ड आवश्यक आहे"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"प्रशासकाद्वारे लॉक केलेले डिव्हाइस"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"डिव्हाइस मॅन्युअली लॉक केले होते"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ओळखले नाही"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"डीफॉल्ट"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"अॅनालॉग"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml
index 453afc3..a6d1af9 100644
--- a/packages/SystemUI/res-keyguard/values-ms/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Corak diperlukan setelah peranti dimulakan semula"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah peranti dimulakan semula"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kata laluan diperlukan setelah peranti dimulakan semula"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Corak diperlukan untuk keselamatan tambahan"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN diperlukan untuk keselamatan tambahan"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Kata laluan diperlukan untuk keselamatan tambahan"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Peranti dikunci oleh pentadbir"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Peranti telah dikunci secara manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Lalai"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Gelembung"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index 1cc46b1..5617a11 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပုံစံ လိုအပ်ပါသည်"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပင်နံပါတ် လိုအပ်ပါသည်"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် စကားဝှက် လိုအပ်ပါသည်"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ပိုမို၍ လုံခြုံမှု ရှိစေရန် ပုံစံ လိုအပ်ပါသည်"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ပိုမို၍ လုံခြုံမှု ရှိစေရန် ပင်နံပါတ် လိုအပ်ပါသည်"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ပိုမို၍ လုံခြုံမှု ရှိစေရန် စကားဝှက် လိုအပ်ပါသည်"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"စက်ပစ္စည်းကို စီမံခန့်ခွဲသူက လော့ခ်ချထားပါသည်"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"စက်ပစ္စည်းကို ကိုယ်တိုင်ကိုယ်ကျ လော့ခ်ချထားခဲ့သည်"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"မသိ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"မူလ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ပူဖောင်းကွက်"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ရိုးရိုး"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml
index 5310a730..0ad9e95 100644
--- a/packages/SystemUI/res-keyguard/values-nb/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du må tegne mønsteret etter at enheten har startet på nytt"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du må skrive inn PIN-koden etter at enheten har startet på nytt"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du må skrive inn passordet etter at enheten har startet på nytt"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Du må tegne mønsteret for ekstra sikkerhet"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Du må skrive inn PIN-koden for ekstra sikkerhet"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Du må skrive inn passordet for ekstra sikkerhet"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Enheten er låst av administratoren"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheten ble låst manuelt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ikke gjenkjent"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Boble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index 534164b..196b74a 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"यन्त्र पुनः सुरु भएपछि ढाँचा आवश्यक पर्दछ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"यन्त्र पुनः सुरु भएपछि PIN आवश्यक पर्दछ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"यन्त्र पुनः सुरु भएपछि पासवर्ड आवश्यक पर्दछ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"अतिरिक्त सुरक्षाको लागि ढाँचा आवश्यक छ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"अतिरिक्त सुरक्षाको लागि PIN आवश्यक छ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"अतिरिक्त सुरक्षाको लागि पासवर्ड आवश्यक छ"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"प्रशासकले यन्त्रलाई लक गर्नुभएको छ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"यन्त्रलाई म्यानुअल तरिकाले लक गरिएको थियो"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"पहिचान भएन"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"डिफल्ट"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"एनालग"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index 08e226d4..747b3bb 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon vereist nadat het apparaat opnieuw is opgestart"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pincode vereist nadat het apparaat opnieuw is opgestart"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wachtwoord vereist nadat het apparaat opnieuw is opgestart"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Patroon vereist voor extra beveiliging"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Pincode vereist voor extra beveiliging"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Wachtwoord vereist voor extra beveiliging"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Apparaat vergrendeld door beheerder"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Apparaat is handmatig vergrendeld"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Niet herkend"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standaard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bel"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-or/strings.xml b/packages/SystemUI/res-keyguard/values-or/strings.xml
index 3cdd264..75f7a89 100644
--- a/packages/SystemUI/res-keyguard/values-or/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-or/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ଡିଭାଇସ୍ ରିଷ୍ଟାର୍ଟ ହେବା ପରେ ପାଟର୍ନ ଆବଶ୍ୟକ ଅଟେ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ଡିଭାଇସ୍ ରିଷ୍ଟାର୍ଟ ହେବାପରେ ପାସ୍ୱର୍ଡ ଆବଶ୍ୟକ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ଡିଭାଇସ୍ ରିଷ୍ଟାର୍ଟ ହେବା ପରେ ପାସୱର୍ଡ ଆବଶ୍ୟକ ଅଟେ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ ପାଟର୍ନ ଆବଶ୍ୟକ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ PIN ଆବଶ୍ୟକ ଅଟେ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ ପାସ୍ୱର୍ଡ ଆବଶ୍ୟକ"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ, ଏହା ପରିବର୍ତ୍ତେ ପାଟର୍ନ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ, ଏହା ପରିବର୍ତ୍ତେ PIN ବ୍ୟବହାର କରନ୍ତୁ"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ, ଏହା ପରିବର୍ତ୍ତେ ପାସୱାର୍ଡ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ଡିଭାଇସ୍ ଆଡମିନଙ୍କ ଦ୍ୱାରା ଲକ୍ କରାଯାଇଛି"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ଡିଭାଇସ୍ ମାନୁଆଲ ଭାବେ ଲକ୍ କରାଗଲା"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ଚିହ୍ନଟ ହେଲାନାହିଁ"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"ଡିଫଲ୍ଟ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ବବଲ୍"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ଆନାଲଗ୍"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ଜାରି ରଖିବା ପାଇଁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ଅନଲକ କରନ୍ତୁ"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index 409f727..bf1a359a 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪੈਟਰਨ ਦੀ ਲੋੜ ਹੈ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਿੰਨ ਦੀ ਲੋੜ ਹੈ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੈ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ ਪੈਟਰਨ ਦੀ ਲੋੜ ਹੈ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ ਪਿੰਨ ਦੀ ਲੋੜ ਹੈ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੈ"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਡੀਵਾਈਸ ਨੂੰ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ਡੀਵਾਈਸ ਨੂੰ ਹੱਥੀਂ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ਬੁਲਬੁਲਾ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ਐਨਾਲੌਗ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml
index 52bc982..c49149b 100644
--- a/packages/SystemUI/res-keyguard/values-pl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po ponownym uruchomieniu urządzenia wymagany jest wzór"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po ponownym uruchomieniu urządzenia wymagany jest kod PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po ponownym uruchomieniu urządzenia wymagane jest hasło"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Dla większego bezpieczeństwa musisz narysować wzór"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Dla większego bezpieczeństwa musisz podać kod PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Dla większego bezpieczeństwa musisz podać hasło"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Urządzenie zablokowane przez administratora"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Urządzenie zostało zablokowane ręcznie"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nie rozpoznano"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Domyślna"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bąbelkowy"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogowy"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
index b934826..3d60e8c 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"O padrão é exigido após a reinicialização do dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"O PIN é exigido após a reinicialização do dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"A senha é exigida após a reinicialização do dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"O padrão é necessário para aumentar a segurança"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"O PIN é necessário para aumentar a segurança"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"A senha é necessária para aumentar a segurança"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para ter mais segurança, use o padrão"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para ter mais segurança, use o PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para ter mais segurança, use a senha"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado pelo administrador"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo foi bloqueado manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Não reconhecido"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Padrão"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bolha"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueie o dispositivo para continuar"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index a67bfb0..0a94349 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"É necessário um padrão após reiniciar o dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"É necessário um PIN após reiniciar o dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"É necessária uma palavra-passe após reiniciar o dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Para segurança adicional, é necessário um padrão"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Para segurança adicional, é necessário um PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Para segurança adicional, é necessária uma palavra-passe"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para uma segurança adicional, use antes o padrão"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para uma segurança adicional, use antes o PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para uma segurança adicional, use antes a palavra-passe"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado pelo gestor"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo foi bloqueado manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Não reconhecido."</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predefinido"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Balão"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueie o dispositivo para continuar"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pt/strings.xml b/packages/SystemUI/res-keyguard/values-pt/strings.xml
index b934826..3d60e8c 100644
--- a/packages/SystemUI/res-keyguard/values-pt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"O padrão é exigido após a reinicialização do dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"O PIN é exigido após a reinicialização do dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"A senha é exigida após a reinicialização do dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"O padrão é necessário para aumentar a segurança"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"O PIN é necessário para aumentar a segurança"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"A senha é necessária para aumentar a segurança"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para ter mais segurança, use o padrão"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para ter mais segurança, use o PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para ter mais segurança, use a senha"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado pelo administrador"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo foi bloqueado manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Não reconhecido"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Padrão"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bolha"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueie o dispositivo para continuar"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index 5ee67d91..547224e 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Modelul este necesar după repornirea dispozitivului"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Codul PIN este necesar după repornirea dispozitivului"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Parola este necesară după repornirea dispozitivului"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Modelul este necesar pentru securitate suplimentară"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Codul PIN este necesar pentru securitate suplimentară"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Parola este necesară pentru securitate suplimentară"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispozitiv blocat de administrator"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Dispozitivul a fost blocat manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nu este recunoscut"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Prestabilit"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogic"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index 2b8f8d6..f1945ad 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"После перезагрузки устройства необходимо ввести графический ключ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"После перезагрузки устройства необходимо ввести PIN-код"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"После перезагрузки устройства необходимо ввести пароль"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"В качестве дополнительной меры безопасности введите графический ключ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"В качестве дополнительной меры безопасности введите PIN-код"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"В качестве дополнительной меры безопасности введите пароль"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"В целях дополнительной безопасности используйте графический ключ"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"В целях дополнительной безопасности используйте PIN-код"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"В целях дополнительной безопасности используйте пароль"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Устройство заблокировано администратором"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Устройство было заблокировано вручную"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Не распознано"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"По умолчанию"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Пузырь"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Стрелки"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Чтобы продолжить, разблокируйте устройство"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml
index 4e911de..e5862c3 100644
--- a/packages/SystemUI/res-keyguard/values-si/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-si/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"උපාංගය නැවත ආරම්භ වූ පසු රටාව අවශ්යයි"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"උපාංගය නැවත ආරම්භ වූ පසු PIN අංකය අවශ්යයි"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"උපාංගය නැවත ආරම්භ වූ පසු මුරපදය අවශ්යයි"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"අමතර ආරක්ෂාව සඳහා රටාව අවශ්යයි"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"අමතර ආරක්ෂාව සඳහා PIN අංකය අවශ්යයි"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"අමතර ආරක්ෂාව සඳහා මුරපදය අවශ්යයි"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ඔබගේ පරිපාලක විසින් උපාංගය අගුළු දමා ඇත"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"උපාංගය හස්තීයව අගුලු දමන ලදී"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"හඳුනා නොගන්නා ලදී"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"පෙරනිමි"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"බුබුළ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ප්රතිසමය"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml
index f2d68e3..efe4ec8 100644
--- a/packages/SystemUI/res-keyguard/values-sk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po reštartovaní zariadenia musíte zadať bezpečnostný vzor"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po reštartovaní zariadenia musíte zadať kód PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po reštartovaní zariadenia musíte zadať heslo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Na ďalšie zabezpečenie musíte zadať bezpečnostný vzor"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Na ďalšie zabezpečenie musíte zadať kód PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Na ďalšie zabezpečenie musíte zadať heslo"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Zariadenie zamkol správca"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Zariadenie bolo uzamknuté ručne"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nerozpoznané"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predvolený"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógový"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml
index 772308f..52726c2 100644
--- a/packages/SystemUI/res-keyguard/values-sl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po vnovičnem zagonu naprave je treba vnesti vzorec"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po vnovičnem zagonu naprave je treba vnesti kodo PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po vnovičnem zagonu naprave je treba vnesti geslo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Zaradi dodatne varnosti morate vnesti vzorec"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Zaradi dodatne varnosti morate vnesti kodo PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Zaradi dodatne varnosti morate vnesti geslo"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Napravo je zaklenil skrbnik"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Naprava je bila ročno zaklenjena"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ni prepoznano"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Privzeto"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mehurček"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogno"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index c758462..a0a5594 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kërkohet motivi pas rinisjes së pajisjes"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Kërkohet kodi PIN pas rinisjes së pajisjes"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kërkohet fjalëkalimi pas rinisjes së pajisjes"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kërkohet motivi për më shumë siguri"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Kërkohet kodi PIN për më shumë siguri"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Kërkohet fjalëkalimi për më shumë siguri"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Pajisja është e kyçur nga administratori"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Pajisja është kyçur manualisht"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nuk njihet"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"E parazgjedhur"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Flluskë"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoge"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index e6fe853..e634fdcb5 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Треба да унесете шаблон када се уређај поново покрене"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Треба да унесете PIN када се уређај поново покрене"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Треба да унесете лозинку када се уређај поново покрене"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Треба да унесете шаблон ради додатне безбедности"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Треба да унесете PIN ради додатне безбедности"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Треба да унесете лозинку ради додатне безбедности"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Администратор је закључао уређај"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Уређај је ручно закључан"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Није препознат"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Подразумевани"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Мехурићи"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналогни"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml
index fa241d9..fc9beb1 100644
--- a/packages/SystemUI/res-keyguard/values-sv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du måste rita mönster när du har startat om enheten"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du måste ange pinkod när du har startat om enheten"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du måste ange lösenord när du har startat om enheten"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Du måste rita mönster för ytterligare säkerhet"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Du måste ange pinkod för ytterligare säkerhet"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Du måste ange lösenord för ytterligare säkerhet"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratören har låst enheten"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheten har låsts manuellt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Identifierades inte"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubbla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml
index 791bceb..bcab24b 100644
--- a/packages/SystemUI/res-keyguard/values-sw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Unafaa kuchora mchoro baada ya kuwasha kifaa upya"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Unafaa kuweka PIN baada ya kuwasha kifaa upya"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Unafaa kuweka nenosiri baada ya kuwasha kifaa upya"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Mchoro unahitajika ili kuongeza usalama"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN inahitajika ili kuongeza usalama"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Nenosiri linahitajika ili kuongeza usalama."</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Msimamizi amefunga kifaa"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Umefunga kifaa mwenyewe"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Haitambuliwi"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Chaguomsingi"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Kiputo"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogi"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 271657d..88d5760 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"சாதனத்தை மீண்டும் தொடங்கியதும், பேட்டர்னை வரைய வேண்டும்"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"சாதனத்தை மீண்டும் தொடங்கியதும், பின்னை உள்ளிட வேண்டும்"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"சாதனத்தை மீண்டும் தொடங்கியதும், கடவுச்சொல்லை உள்ளிட வேண்டும்"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"கூடுதல் பாதுகாப்பிற்கு, பேட்டர்னை வரைய வேண்டும்"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"கூடுதல் பாதுகாப்பிற்கு, பின்னை உள்ளிட வேண்டும்"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"கூடுதல் பாதுகாப்பிற்கு, கடவுச்சொல்லை உள்ளிட வேண்டும்"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"நிர்வாகி சாதனத்தைப் பூட்டியுள்ளார்"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"பயனர் சாதனத்தைப் பூட்டியுள்ளார்"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"அடையாளங்காணபடவில்லை"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"இயல்பு"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"பபிள்"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"அனலாக்"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index f62e667..3a0111a 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత నమూనాను గీయాలి"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"డివైజ్ను పునఃప్రారంభించిన తర్వాత పిన్ నమోదు చేయాలి"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత పాస్వర్డ్ను నమోదు చేయాలి"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"అదనపు సెక్యూరిటీ కోసం ఆకృతి అవసరం"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"అదనపు సెక్యూరిటీ కోసం పిన్ ఎంటర్ చేయాలి"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"అదనపు సెక్యూరిటీ కోసం పాస్వర్డ్ను ఎంటర్ చేయాలి"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"పరికరం నిర్వాహకుల ద్వారా లాక్ చేయబడింది"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"పరికరం మాన్యువల్గా లాక్ చేయబడింది"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"గుర్తించలేదు"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ఆటోమేటిక్"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"బబుల్"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ఎనలాగ్"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-th/strings.xml b/packages/SystemUI/res-keyguard/values-th/strings.xml
index 62a83bc..14a65a07 100644
--- a/packages/SystemUI/res-keyguard/values-th/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-th/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ต้องวาดรูปแบบหลังจากอุปกรณ์รีสตาร์ท"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ต้องระบุ PIN หลังจากอุปกรณ์รีสตาร์ท"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ต้องป้อนรหัสผ่านหลังจากอุปกรณ์รีสตาร์ท"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ต้องวาดรูปแบบเพื่อความปลอดภัยเพิ่มเติม"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ต้องระบุ PIN เพื่อความปลอดภัยเพิ่มเติม"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ต้องป้อนรหัสผ่านเพื่อความปลอดภัยเพิ่มเติม"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ใช้รูปแบบแทนเพื่อเพิ่มความปลอดภัย"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ใช้ PIN แทนเพื่อเพิ่มความปลอดภัย"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ใช้รหัสผ่านแทนเพื่อเพิ่มความปลอดภัย"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ผู้ดูแลระบบล็อกอุปกรณ์"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"มีการล็อกอุปกรณ์ด้วยตัวเอง"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ไม่รู้จัก"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"ค่าเริ่มต้น"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"บับเบิล"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"แอนะล็อก"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ปลดล็อกอุปกรณ์ของคุณเพื่อดำเนินการต่อ"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-tl/strings.xml b/packages/SystemUI/res-keyguard/values-tl/strings.xml
index 524ea47..7936058 100644
--- a/packages/SystemUI/res-keyguard/values-tl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tl/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kailangan ng pattern pagkatapos mag-restart ng device"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Kailangan ng PIN pagkatapos mag-restart ng device"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kailangan ng password pagkatapos mag-restart ng device"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kinakailangan ang pattern para sa karagdagang seguridad"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Kinakailangan ang PIN para sa karagdagang seguridad"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Kinakailangan ang password para sa karagdagang seguridad"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para sa karagdagang seguridad, gumamit na lang ng pattern"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para sa karagdagang seguridad, gumamit na lang ng PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para sa karagdagang seguridad, gumamit na lang ng password"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Na-lock ng admin ang device"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Manual na na-lock ang device"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Hindi nakilala"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"I-unlock ang iyong device para magpatuloy"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml
index 54aaae3..e520762 100644
--- a/packages/SystemUI/res-keyguard/values-tr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yeniden başladıktan sonra desen gerekir"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıktan sonra PIN gerekir"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıktan sonra şifre gerekir"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Ek güvenlik için desen gerekir"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Ek güvenlik için PIN gerekir"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Ek güvenlik için şifre gerekir"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Cihaz, yönetici tarafından kilitlendi"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Cihazın manuel olarak kilitlendi"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tanınmadı"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Varsayılan"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Baloncuk"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml
index 6144c1c..613181d 100644
--- a/packages/SystemUI/res-keyguard/values-uk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Після перезавантаження пристрою потрібно ввести ключ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Після перезавантаження пристрою потрібно ввести PIN-код"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Після перезавантаження пристрою потрібно ввести пароль"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Для додаткового захисту потрібно ввести ключ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Для додаткового захисту потрібно ввести PIN-код"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Для додаткового захисту потрібно ввести пароль"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Адміністратор заблокував пристрій"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Пристрій заблоковано вручну"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Не розпізнано"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"За умовчанням"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Бульбашковий"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналоговий"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index 4e77841..a122f85 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"آلہ دوبارہ چالو ہونے کے بعد پیٹرن درکار ہوتا ہے"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"آلہ دوبارہ چالو ہونے کے بعد PIN درکار ہوتا ہے"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"آلہ دوبارہ چالو ہونے کے بعد پاس ورڈ درکار ہوتا ہے"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"اضافی سیکیورٹی کیلئے پیٹرن درکار ہے"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"اضافی سیکیورٹی کیلئے PIN درکار ہے"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"اضافی سیکیورٹی کیلئے پاس ورڈ درکار ہے"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"آلہ منتظم کی جانب سے مقفل ہے"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"آلہ کو دستی طور پر مقفل کیا گیا تھا"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"تسلیم شدہ نہیں ہے"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ڈیفالٹ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"بلبلہ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"اینالاگ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index afaf746..2cc9724 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Qurilma qayta ishga tushganidan keyin grafik kalitni kiritish zarur"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Qurilma qayta ishga tushganidan keyin PIN kodni kiritish zarur"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Qurilma qayta ishga tushganidan keyin parolni kiritish zarur"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Qo‘shimcha xavfsizlik chorasi sifatida grafik kalit talab qilinadi"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Qo‘shimcha xavfsizlik chorasi sifatida PIN kod talab qilinadi"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Qo‘shimcha xavfsizlik chorasi sifatida parol talab qilinadi"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Qoʻshimcha xavfsizlik maqsadida oʻrniga grafik kalitdan foydalaning"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Qoʻshimcha xavfsizlik maqsadida oʻrniga PIN koddan foydalaning"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Qoʻshimcha xavfsizlik maqsadida oʻrniga paroldan foydalaning"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Qurilma administrator tomonidan bloklangan"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Qurilma qo‘lda qulflangan"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Aniqlanmadi"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Odatiy"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Pufaklar"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Davom etish uchun qurilmangizni qulfdan chiqaring"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml
index 1d6cfa8..e7c9295 100644
--- a/packages/SystemUI/res-keyguard/values-vi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Yêu cầu hình mở khóa sau khi thiết bị khởi động lại"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Yêu cầu mã PIN sau khi thiết bị khởi động lại"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Yêu cầu mật khẩu sau khi thiết bị khởi động lại"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Yêu cầu hình mở khóa để bảo mật thêm"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Yêu cầu mã PIN để bảo mật thêm"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Yêu cầu mật khẩu để bảo mật thêm"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Thiết bị đã bị quản trị viên khóa"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Thiết bị đã bị khóa theo cách thủ công"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Không nhận dạng được"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Mặc định"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bong bóng"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Đồng hồ kim"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index 8c8507e..d37d645 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"重启设备后需要绘制解锁图案"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"重启设备后需要输入 PIN 码"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"重启设备后需要输入密码"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"需要绘制解锁图案以进一步确保安全"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"需要输入 PIN 码以进一步确保安全"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"需要输入密码以进一步确保安全"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"管理员已锁定设备"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"此设备已手动锁定"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"无法识别"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"默认"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"指针"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
index c331a92..9dbb8f2 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後,必須畫出上鎖圖案才能使用"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後,必須輸入 PIN 碼才能使用"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後,必須輸入密碼才能使用"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"請務必畫出上鎖圖案,以進一步確保安全"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"請務必輸入 PIN 碼,以進一步確保安全"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"請務必輸入密碼,以進一步確保安全"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"裝置已由管理員鎖定"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"使用者已手動將裝置上鎖"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"未能識別"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"預設"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"指針"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
index 1e1bec3..ebb88e1 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後需要畫出解鎖圖案"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後需要輸入 PIN 碼"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後需要輸入密碼"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"請畫出解鎖圖案,以進一步確保資訊安全"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"請輸入 PIN 碼,以進一步確保資訊安全"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"請輸入密碼,以進一步確保資訊安全"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"管理員已鎖定裝置"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"裝置已手動鎖定"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"無法識別"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"預設"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"類比"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml
index c8f78ea..57e56f7 100644
--- a/packages/SystemUI/res-keyguard/values-zu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iphethini iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iphinikhodi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iphasiwedi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kudingeka iphethini ngokuvikeleka okungeziwe"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Kudingeka iphinikhodi ngokuvikeleka okungeziwe"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Iphasiwedi idingelwa ukuvikela okungeziwe"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Idivayisi ikhiywe ngumlawuli"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Idivayisi ikhiywe ngokwenza"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Akwaziwa"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Okuzenzekelayo"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Ibhamuza"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"I-Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/drawable/accessibility_floating_message_background.xml b/packages/SystemUI/res/drawable/accessibility_floating_message_background.xml
new file mode 100644
index 0000000..de83df4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_floating_message_background.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/accessibility_floating_menu_message_background"/>
+ <corners android:radius="28dp"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/overlay_badge_background.xml b/packages/SystemUI/res/drawable/overlay_badge_background.xml
new file mode 100644
index 0000000..857632e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/overlay_badge_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="oval">
+ <solid android:color="?androidprv:attr/colorSurface"/>
+</shape>
diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
index 3bcc37a..e2ce34f 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthCredentialPasswordView
+<com.android.systemui.biometrics.ui.CredentialPasswordView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -86,4 +86,4 @@
</LinearLayout>
-</com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
+</com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
index a3dd334..6e0e38b 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthCredentialPatternView
+<com.android.systemui.biometrics.ui.CredentialPatternView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -83,4 +83,4 @@
</FrameLayout>
-</com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file
+</com.android.systemui.biometrics.ui.CredentialPatternView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 774b335f..021ebe6 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthCredentialPasswordView
+<com.android.systemui.biometrics.ui.CredentialPasswordView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -83,4 +83,4 @@
</LinearLayout>
-</com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
+</com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 4af9970..891c6af 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthCredentialPatternView
+<com.android.systemui.biometrics.ui.CredentialPatternView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -78,4 +78,4 @@
android:layout_gravity="center_horizontal|bottom"/>
</FrameLayout>
-</com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file
+</com.android.systemui.biometrics.ui.CredentialPatternView>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 5b96159..ae2537f 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -190,6 +190,11 @@
</LinearLayout>
+ <ViewStub android:id="@+id/secondary_mobile_network_stub"
+ android:inflatedId="@+id/secondary_mobile_network_layout"
+ android:layout="@layout/qs_dialog_secondary_mobile_network"
+ style="@style/InternetDialog.Network"/>
+
<LinearLayout
android:id="@+id/turn_on_wifi_layout"
style="@style/InternetDialog.Network"
diff --git a/packages/SystemUI/res/layout/qs_dialog_secondary_mobile_network.xml b/packages/SystemUI/res/layout/qs_dialog_secondary_mobile_network.xml
new file mode 100644
index 0000000..4592c5e
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_dialog_secondary_mobile_network.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/InternetDialog.Network">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start">
+ <ImageView
+ android:id="@+id/secondary_signal_icon"
+ android:autoMirrored="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical">
+ <TextView
+ android:id="@+id/secondary_mobile_title"
+ android:maxLines="1"
+ style="@style/InternetDialog.NetworkTitle"/>
+ <TextView
+ android:id="@+id/secondary_mobile_summary"
+ style="@style/InternetDialog.NetworkSummary"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="match_parent"
+ android:clickable="false"
+ android:layout_gravity="end|center_vertical"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/secondary_settings_icon"
+ android:src="@drawable/ic_settings_24dp"
+ android:layout_width="24dp"
+ android:layout_gravity="end|center_vertical"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 2fb6d6c..9fc3f40 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -23,6 +23,7 @@
android:layout_height="wrap_content"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:background="@android:color/transparent"
+ android:importantForAccessibility="no"
android:baselineAligned="false"
android:clickable="false"
android:clipChildren="false"
@@ -56,7 +57,7 @@
android:clipToPadding="false"
android:focusable="true"
android:paddingBottom="@dimen/qqs_layout_padding_bottom"
- android:importantForAccessibility="yes">
+ android:importantForAccessibility="no">
</com.android.systemui.qs.QuickQSPanel>
</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
index 60bc373..8b5d953 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
@@ -25,6 +25,7 @@
android:gravity="center"
android:layout_gravity="top"
android:orientation="horizontal"
+ android:importantForAccessibility="no"
android:clickable="true"
android:minHeight="48dp">
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 9c02749..1ac78d4 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -103,8 +103,18 @@
app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"
- app:layout_constraintTop_toTopOf="@id/screenshot_preview_border">
- </ImageView>
+ app:layout_constraintTop_toTopOf="@id/screenshot_preview_border"/>
+ <ImageView
+ android:id="@+id/screenshot_badge"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:padding="4dp"
+ android:visibility="gone"
+ android:background="@drawable/overlay_badge_background"
+ android:elevation="8dp"
+ android:src="@drawable/overlay_cancel"
+ app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
+ app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/>
<FrameLayout
android:id="@+id/screenshot_dismiss_button"
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index dc2bee5..16152f8 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -99,6 +99,8 @@
<!-- Accessibility floating menu -->
<color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
+ <color name="accessibility_floating_menu_message_background">@*android:color/background_material_dark</color>
+ <color name="accessibility_floating_menu_message_text">@*android:color/primary_text_default_material_dark</color>
<color name="people_tile_background">@color/material_dynamic_secondary20</color>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 5f1863a..8a1a9e2 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -672,7 +672,7 @@
<string name="data_connection_no_internet" msgid="691058178914184544">"沒有網際網路連線"</string>
<string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"開啟「<xliff:g id="ID_1">%s</xliff:g>」設定。"</string>
<string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"編輯設定順序。"</string>
- <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源按鈕選單"</string>
+ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源鍵選單"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"第 <xliff:g id="ID_1">%1$d</xliff:g> 頁,共 <xliff:g id="ID_2">%2$d</xliff:g> 頁"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"鎖定畫面"</string>
<string name="thermal_shutdown_title" msgid="2702966892682930264">"手機先前過熱,因此關閉電源"</string>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 9e8bef0..55b59b6 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -219,6 +219,8 @@
<!-- Accessibility floating menu -->
<color name="accessibility_floating_menu_background">#CCFFFFFF</color> <!-- 80% -->
<color name="accessibility_floating_menu_stroke_dark">#26FFFFFF</color> <!-- 15% -->
+ <color name="accessibility_floating_menu_message_background">@*android:color/background_material_light</color>
+ <color name="accessibility_floating_menu_message_text">@*android:color/primary_text_default_material_light</color>
<!-- Wallet screen -->
<color name="wallet_card_border">#33FFFFFF</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9188ce0..93982cb 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -643,6 +643,18 @@
<item>26</item> <!-- MOUTH_COVERING_DETECTED -->
</integer-array>
+ <!-- Which device wake-ups will trigger face auth. These values correspond with
+ PowerManager#WakeReason. -->
+ <integer-array name="config_face_auth_wake_up_triggers">
+ <item>1</item> <!-- WAKE_REASON_POWER_BUTTON -->
+ <item>4</item> <!-- WAKE_REASON_GESTURE -->
+ <item>6</item> <!-- WAKE_REASON_WAKE_KEY -->
+ <item>7</item> <!-- WAKE_REASON_WAKE_MOTION -->
+ <item>9</item> <!-- WAKE_REASON_LID -->
+ <item>10</item> <!-- WAKE_REASON_DISPLAY_GROUP_ADDED -->
+ <item>12</item> <!-- WAKE_REASON_UNFOLD_DEVICE -->
+ </integer-array>
+
<!-- Whether the communal service should be enabled -->
<bool name="config_communalServiceEnabled">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 66f0e75..f02f29a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1336,6 +1336,14 @@
<dimen name="accessibility_floating_menu_large_single_radius">35dp</dimen>
<dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen>
+ <dimen name="accessibility_floating_menu_message_container_horizontal_padding">15dp</dimen>
+ <dimen name="accessibility_floating_menu_message_text_vertical_padding">8dp</dimen>
+ <dimen name="accessibility_floating_menu_message_margin">8dp</dimen>
+ <dimen name="accessibility_floating_menu_message_elevation">5dp</dimen>
+ <dimen name="accessibility_floating_menu_message_text_size">14sp</dimen>
+ <dimen name="accessibility_floating_menu_message_min_width">312dp</dimen>
+ <dimen name="accessibility_floating_menu_message_min_height">48dp</dimen>
+
<dimen name="accessibility_floating_tooltip_arrow_width">8dp</dimen>
<dimen name="accessibility_floating_tooltip_arrow_height">16dp</dimen>
<dimen name="accessibility_floating_tooltip_arrow_margin">-2dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 7ca42f7..4fd25a9 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -177,6 +177,7 @@
<item type="id" name="action_move_bottom_right"/>
<item type="id" name="action_move_to_edge_and_hide"/>
<item type="id" name="action_move_out_edge_and_show"/>
+ <item type="id" name="action_remove_menu"/>
<!-- rounded corner view id -->
<item type="id" name="rounded_corner_top_left"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d4d8843..b325c56 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2022,6 +2022,15 @@
<!-- Text used to refer to the user's current carrier in mobile_data_disable_message if the users's mobile network carrier name is not available [CHAR LIMIT=NONE] -->
<string name="mobile_data_disable_message_default_carrier">your carrier</string>
+ <!-- Title of the dialog to turn off data usage [CHAR LIMIT=NONE] -->
+ <string name="auto_data_switch_disable_title">Switch back to <xliff:g id="carrier" example="T-Mobile">%s</xliff:g>?</string>
+ <!-- Message body of the dialog to turn off data usage [CHAR LIMIT=NONE] -->
+ <string name="auto_data_switch_disable_message">Mobile data won\’t automatically switch based on availability</string>
+ <!-- Negative button title of the quick settings switch back to DDS dialog [CHAR LIMIT=NONE] -->
+ <string name="auto_data_switch_dialog_negative_button">No thanks</string>
+ <!-- Positive button title of the quick settings switch back to DDS dialog [CHAR LIMIT=NONE] -->
+ <string name="auto_data_switch_dialog_positive_button">Yes, switch</string>
+
<!-- Warning shown when user input has been blocked due to another app overlaying screen
content. Since we don't know what the app is showing on top of the input target, we
can't verify user consent. [CHAR LIMIT=NONE] -->
@@ -2188,6 +2197,15 @@
<string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string>
<!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] -->
<string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
+ <!-- Text for the undo action button of the message view of the accessibility floating menu to perform undo operation. [CHAR LIMIT=30]-->
+ <string name="accessibility_floating_button_undo">Undo</string>
+
+ <!-- Text for the message view with undo action of the accessibility floating menu to show how many features shortcuts were removed. [CHAR LIMIT=30]-->
+ <string name="accessibility_floating_button_undo_message_text">{count, plural,
+ =1 {{label} shortcut removed}
+ other {# shortcuts removed}
+ }</string>
+
<!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] -->
<string name="accessibility_floating_button_action_move_top_left">Move top left</string>
<!-- Action in accessibility menu to move the accessibility floating button to the top right of the screen. [CHAR LIMIT=30] -->
@@ -2200,6 +2218,8 @@
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half">Move to edge and hide</string>
<!-- Action in accessibility menu to move the accessibility floating button out the edge and show. [CHAR LIMIT=36]-->
<string name="accessibility_floating_button_action_move_out_edge_and_show">Move out edge and show</string>
+ <!-- Action in accessibility menu to remove the accessibility floating menu view on the screen. [CHAR LIMIT=36]-->
+ <string name="accessibility_floating_button_action_remove_menu">Remove</string>
<!-- Action in accessibility menu to toggle on/off the accessibility feature. [CHAR LIMIT=30]-->
<string name="accessibility_floating_button_action_double_tap_to_toggle">toggle</string>
@@ -2527,6 +2547,12 @@
Summary indicating that a SIM has an active mobile data connection [CHAR LIMIT=50] -->
<string name="mobile_data_connection_active">Connected</string>
<!-- Provider Model:
+ Summary indicating that a SIM is temporarily connected to mobile data [CHAR LIMIT=50] -->
+ <string name="mobile_data_temp_connection_active">Temporarily connected</string>
+ <!-- Provider Model:
+ Summary indicating that a SIM is temporarily connected to mobile data [CHAR LIMIT=50] -->
+ <string name="mobile_data_poor_connection">Poor connection</string>
+ <!-- Provider Model:
Summary indicating that a SIM has no mobile data connection [CHAR LIMIT=50] -->
<string name="mobile_data_off_summary">Mobile data won\u0027t auto\u2011connect</string>
<!-- Provider Model:
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index cd27263..48821e8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -18,7 +18,6 @@
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Handler
-import android.os.UserHandle
import android.provider.Settings
import android.util.Log
import com.android.internal.annotations.Keep
@@ -39,15 +38,15 @@
val context: Context,
val pluginManager: PluginManager,
val handler: Handler,
- defaultClockProvider: ClockProvider
+ val isEnabled: Boolean,
+ userHandle: Int,
+ defaultClockProvider: ClockProvider,
) {
// Usually this would be a typealias, but a SAM provides better java interop
fun interface ClockChangeListener {
fun onClockChanged()
}
- var isEnabled: Boolean = false
-
private val gson = Gson()
private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
@@ -97,14 +96,19 @@
)
}
- pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java,
- true /* allowMultiple */)
- context.contentResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
- false,
- settingObserver,
- UserHandle.USER_ALL
- )
+ if (isEnabled) {
+ pluginManager.addPluginListener(
+ pluginListener,
+ ClockProviderPlugin::class.java,
+ /*allowMultiple=*/ true
+ )
+ context.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+ /*notifyForDescendants=*/ false,
+ settingObserver,
+ userHandle
+ )
+ }
}
private fun connectClocks(provider: ClockProvider) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 7e42e1b..8ac1de8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -85,13 +85,12 @@
mTmpSourceRectF.set(sourceBounds);
mTmpDestinationRect.set(sourceBounds);
mTmpDestinationRect.inset(insets);
- // Scale by the shortest edge and offset such that the top/left of the scaled inset
- // source rect aligns with the top/left of the destination bounds
+ // Scale to the bounds no smaller than the destination and offset such that the top/left
+ // of the scaled inset source rect aligns with the top/left of the destination bounds
final float scale;
if (sourceRectHint.isEmpty() || sourceRectHint.width() == sourceBounds.width()) {
- scale = sourceBounds.width() <= sourceBounds.height()
- ? (float) destinationBounds.width() / sourceBounds.width()
- : (float) destinationBounds.height() / sourceBounds.height();
+ scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
+ (float) destinationBounds.height() / sourceBounds.height());
} else {
// scale by sourceRectHint if it's not edge-to-edge
final float endScale = sourceRectHint.width() <= sourceRectHint.height()
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 8d1768c..e1e8063 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
@@ -26,6 +26,7 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -228,7 +229,8 @@
public static RemoteAnimationTarget[] wrapNonApps(TransitionInfo info, boolean wallpapers,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
return wrap(info, t, leashMap, (change, taskInfo) -> (taskInfo == null)
- && wallpapers == ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0));
+ && wallpapers == change.hasFlags(FLAG_IS_WALLPAPER)
+ && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY));
}
private static RemoteAnimationTarget[] wrap(TransitionInfo info,
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index bb3df8f..7b216017 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -18,17 +18,15 @@
import android.content.Context
import android.os.Handler
-import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlagsDebug.ALL_FLAGS
import com.android.systemui.util.settings.SettingsUtilModule
import dagger.Binds
import dagger.Module
import dagger.Provides
-import javax.inject.Named
@Module(includes = [
FeatureFlagsDebugStartableModule::class,
+ FlagsCommonModule::class,
ServerFlagReaderModule::class,
SettingsUtilModule::class,
])
@@ -43,20 +41,5 @@
fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager {
return FlagManager(context, handler)
}
-
- @JvmStatic
- @Provides
- @Named(ALL_FLAGS)
- fun providesAllFlags(): Map<Int, Flag<*>> = Flags.collectFlags()
-
- @JvmStatic
- @Provides
- fun providesRestarter(barService: IStatusBarService): Restarter {
- return object: Restarter {
- override fun restart() {
- barService.restart()
- }
- }
- }
}
}
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 0f7e732..aef8876 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -16,29 +16,15 @@
package com.android.systemui.flags
-import com.android.internal.statusbar.IStatusBarService
import dagger.Binds
import dagger.Module
-import dagger.Provides
@Module(includes = [
FeatureFlagsReleaseStartableModule::class,
+ FlagsCommonModule::class,
ServerFlagReaderModule::class
])
abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
-
- @Module
- companion object {
- @JvmStatic
- @Provides
- fun providesRestarter(barService: IStatusBarService): Restarter {
- return object: Restarter {
- override fun restart() {
- barService.restart()
- }
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 386c095..40a96b0 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -30,6 +30,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.DOZING_MIGRATION_1
import com.android.systemui.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -221,8 +222,11 @@
disposableHandle = parent.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
listenForDozing(this)
- listenForDozeAmount(this)
- listenForDozeAmountTransition(this)
+ if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ listenForDozeAmountTransition(this)
+ } else {
+ listenForDozeAmount(this)
+ }
}
}
}
@@ -265,10 +269,9 @@
@VisibleForTesting
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.aodToLockscreenTransition.collect {
- // Would eventually run this:
- // dozeAmount = it.value
- // clock?.animations?.doze(dozeAmount)
+ keyguardTransitionInteractor.dozeAmountTransition.collect {
+ dozeAmount = it.value
+ clock?.animations?.doze(dozeAmount)
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
index 6fcb6f5..4a41b3f 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
@@ -17,6 +17,7 @@
package com.android.keyguard
import android.annotation.StringDef
+import android.os.PowerManager
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.keyguard.FaceAuthApiRequestReason.Companion.NOTIFICATION_PANEL_CLICKED
@@ -122,122 +123,93 @@
"Face auth started/stopped because biometric is enabled on keyguard"
}
-/** UiEvents that are logged to identify why face auth is being triggered. */
-enum class FaceAuthUiEvent constructor(private val id: Int, val reason: String) :
+/**
+ * UiEvents that are logged to identify why face auth is being triggered.
+ * @param extraInfo is logged as the position. See [UiEventLogger#logWithInstanceIdAndPosition]
+ */
+enum class FaceAuthUiEvent
+constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) :
UiEventLogger.UiEventEnum {
@UiEvent(doc = OCCLUDING_APP_REQUESTED)
FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED(1146, OCCLUDING_APP_REQUESTED),
-
@UiEvent(doc = UDFPS_POINTER_DOWN)
FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN(1147, UDFPS_POINTER_DOWN),
-
@UiEvent(doc = SWIPE_UP_ON_BOUNCER)
FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER(1148, SWIPE_UP_ON_BOUNCER),
-
@UiEvent(doc = DEVICE_WOKEN_UP_ON_REACH_GESTURE)
FACE_AUTH_TRIGGERED_ON_REACH_GESTURE_ON_AOD(1149, DEVICE_WOKEN_UP_ON_REACH_GESTURE),
-
@UiEvent(doc = FACE_LOCKOUT_RESET)
FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET(1150, FACE_LOCKOUT_RESET),
-
- @UiEvent(doc = QS_EXPANDED)
- FACE_AUTH_TRIGGERED_QS_EXPANDED(1151, QS_EXPANDED),
-
+ @UiEvent(doc = QS_EXPANDED) FACE_AUTH_TRIGGERED_QS_EXPANDED(1151, QS_EXPANDED),
@UiEvent(doc = NOTIFICATION_PANEL_CLICKED)
FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED(1152, NOTIFICATION_PANEL_CLICKED),
-
@UiEvent(doc = PICK_UP_GESTURE_TRIGGERED)
FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED(1153, PICK_UP_GESTURE_TRIGGERED),
-
@UiEvent(doc = ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
- FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN(1154,
- ALTERNATE_BIOMETRIC_BOUNCER_SHOWN),
-
+ FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN(1154, ALTERNATE_BIOMETRIC_BOUNCER_SHOWN),
@UiEvent(doc = PRIMARY_BOUNCER_SHOWN)
FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN(1155, PRIMARY_BOUNCER_SHOWN),
-
@UiEvent(doc = PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN)
FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN(
1197,
PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
),
-
@UiEvent(doc = RETRY_AFTER_HW_UNAVAILABLE)
FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE(1156, RETRY_AFTER_HW_UNAVAILABLE),
-
- @UiEvent(doc = TRUST_DISABLED)
- FACE_AUTH_TRIGGERED_TRUST_DISABLED(1158, TRUST_DISABLED),
-
- @UiEvent(doc = TRUST_ENABLED)
- FACE_AUTH_STOPPED_TRUST_ENABLED(1173, TRUST_ENABLED),
-
+ @UiEvent(doc = TRUST_DISABLED) FACE_AUTH_TRIGGERED_TRUST_DISABLED(1158, TRUST_DISABLED),
+ @UiEvent(doc = TRUST_ENABLED) FACE_AUTH_STOPPED_TRUST_ENABLED(1173, TRUST_ENABLED),
@UiEvent(doc = KEYGUARD_OCCLUSION_CHANGED)
FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED(1159, KEYGUARD_OCCLUSION_CHANGED),
-
@UiEvent(doc = ASSISTANT_VISIBILITY_CHANGED)
FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED(1160, ASSISTANT_VISIBILITY_CHANGED),
-
@UiEvent(doc = STARTED_WAKING_UP)
- FACE_AUTH_UPDATED_STARTED_WAKING_UP(1161, STARTED_WAKING_UP),
-
+ FACE_AUTH_UPDATED_STARTED_WAKING_UP(1161, STARTED_WAKING_UP) {
+ override fun extraInfoToString(): String {
+ return PowerManager.wakeReasonToString(extraInfo)
+ }
+ },
+ @Deprecated(
+ "Not a face auth trigger.",
+ ReplaceWith(
+ "FACE_AUTH_UPDATED_STARTED_WAKING_UP, " +
+ "extraInfo=PowerManager.WAKE_REASON_DREAM_FINISHED"
+ )
+ )
@UiEvent(doc = DREAM_STOPPED)
FACE_AUTH_TRIGGERED_DREAM_STOPPED(1162, DREAM_STOPPED),
-
@UiEvent(doc = ALL_AUTHENTICATORS_REGISTERED)
FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED(1163, ALL_AUTHENTICATORS_REGISTERED),
-
@UiEvent(doc = ENROLLMENTS_CHANGED)
FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED(1164, ENROLLMENTS_CHANGED),
-
@UiEvent(doc = KEYGUARD_VISIBILITY_CHANGED)
FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED(1165, KEYGUARD_VISIBILITY_CHANGED),
-
@UiEvent(doc = FACE_CANCEL_NOT_RECEIVED)
FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED(1174, FACE_CANCEL_NOT_RECEIVED),
-
@UiEvent(doc = AUTH_REQUEST_DURING_CANCELLATION)
FACE_AUTH_TRIGGERED_DURING_CANCELLATION(1175, AUTH_REQUEST_DURING_CANCELLATION),
-
- @UiEvent(doc = DREAM_STARTED)
- FACE_AUTH_STOPPED_DREAM_STARTED(1176, DREAM_STARTED),
-
- @UiEvent(doc = FP_LOCKED_OUT)
- FACE_AUTH_STOPPED_FP_LOCKED_OUT(1177, FP_LOCKED_OUT),
-
+ @UiEvent(doc = DREAM_STARTED) FACE_AUTH_STOPPED_DREAM_STARTED(1176, DREAM_STARTED),
+ @UiEvent(doc = FP_LOCKED_OUT) FACE_AUTH_STOPPED_FP_LOCKED_OUT(1177, FP_LOCKED_OUT),
@UiEvent(doc = FACE_AUTH_STOPPED_ON_USER_INPUT)
FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER(1178, FACE_AUTH_STOPPED_ON_USER_INPUT),
-
@UiEvent(doc = KEYGUARD_GOING_AWAY)
FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY(1179, KEYGUARD_GOING_AWAY),
-
- @UiEvent(doc = CAMERA_LAUNCHED)
- FACE_AUTH_UPDATED_CAMERA_LAUNCHED(1180, CAMERA_LAUNCHED),
-
- @UiEvent(doc = FP_AUTHENTICATED)
- FACE_AUTH_UPDATED_FP_AUTHENTICATED(1181, FP_AUTHENTICATED),
-
- @UiEvent(doc = GOING_TO_SLEEP)
- FACE_AUTH_UPDATED_GOING_TO_SLEEP(1182, GOING_TO_SLEEP),
-
+ @UiEvent(doc = CAMERA_LAUNCHED) FACE_AUTH_UPDATED_CAMERA_LAUNCHED(1180, CAMERA_LAUNCHED),
+ @UiEvent(doc = FP_AUTHENTICATED) FACE_AUTH_UPDATED_FP_AUTHENTICATED(1181, FP_AUTHENTICATED),
+ @UiEvent(doc = GOING_TO_SLEEP) FACE_AUTH_UPDATED_GOING_TO_SLEEP(1182, GOING_TO_SLEEP),
@UiEvent(doc = FINISHED_GOING_TO_SLEEP)
FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP(1183, FINISHED_GOING_TO_SLEEP),
-
- @UiEvent(doc = KEYGUARD_INIT)
- FACE_AUTH_UPDATED_ON_KEYGUARD_INIT(1189, KEYGUARD_INIT),
-
- @UiEvent(doc = KEYGUARD_RESET)
- FACE_AUTH_UPDATED_KEYGUARD_RESET(1185, KEYGUARD_RESET),
-
- @UiEvent(doc = USER_SWITCHING)
- FACE_AUTH_UPDATED_USER_SWITCHING(1186, USER_SWITCHING),
-
+ @UiEvent(doc = KEYGUARD_INIT) FACE_AUTH_UPDATED_ON_KEYGUARD_INIT(1189, KEYGUARD_INIT),
+ @UiEvent(doc = KEYGUARD_RESET) FACE_AUTH_UPDATED_KEYGUARD_RESET(1185, KEYGUARD_RESET),
+ @UiEvent(doc = USER_SWITCHING) FACE_AUTH_UPDATED_USER_SWITCHING(1186, USER_SWITCHING),
@UiEvent(doc = FACE_AUTHENTICATED)
FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED(1187, FACE_AUTHENTICATED),
-
@UiEvent(doc = BIOMETRIC_ENABLED)
FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD(1188, BIOMETRIC_ENABLED);
override fun getId(): Int = this.id
+
+ /** Convert [extraInfo] to a human-readable string. By default, this is empty. */
+ open fun extraInfoToString(): String = ""
}
private val apiRequestReasonToUiEvent =
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt b/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt
new file mode 100644
index 0000000..a0c43fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.keyguard
+
+import android.content.res.Resources
+import android.os.Build
+import android.os.PowerManager
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.settings.GlobalSettings
+import java.io.PrintWriter
+import java.util.stream.Collectors
+import javax.inject.Inject
+
+/** Determines which device wake-ups should trigger face authentication. */
+@SysUISingleton
+class FaceWakeUpTriggersConfig
+@Inject
+constructor(@Main resources: Resources, globalSettings: GlobalSettings, dumpManager: DumpManager) :
+ Dumpable {
+ private val defaultTriggerFaceAuthOnWakeUpFrom: Set<Int> =
+ resources.getIntArray(R.array.config_face_auth_wake_up_triggers).toSet()
+ private val triggerFaceAuthOnWakeUpFrom: Set<Int>
+
+ init {
+ triggerFaceAuthOnWakeUpFrom =
+ if (Build.IS_DEBUGGABLE) {
+ // Update face wake triggers via adb on debuggable builds:
+ // ie: adb shell settings put global face_wake_triggers "1\|4" &&
+ // adb shell am crash com.android.systemui
+ processStringArray(
+ globalSettings.getString("face_wake_triggers"),
+ defaultTriggerFaceAuthOnWakeUpFrom
+ )
+ } else {
+ defaultTriggerFaceAuthOnWakeUpFrom
+ }
+ dumpManager.registerDumpable(this)
+ }
+
+ fun shouldTriggerFaceAuthOnWakeUpFrom(@PowerManager.WakeReason pmWakeReason: Int): Boolean {
+ return triggerFaceAuthOnWakeUpFrom.contains(pmWakeReason)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("FaceWakeUpTriggers:")
+ for (pmWakeReason in triggerFaceAuthOnWakeUpFrom) {
+ pw.println(" ${PowerManager.wakeReasonToString(pmWakeReason)}")
+ }
+ }
+
+ /** Convert a pipe-separated set of integers into a set of ints. */
+ private fun processStringArray(stringSetting: String?, default: Set<Int>): Set<Int> {
+ return stringSetting?.let {
+ stringSetting.split("|").stream().map(Integer::parseInt).collect(Collectors.toSet())
+ }
+ ?: default
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 8eebe30..ace942d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -37,8 +37,6 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
@@ -120,8 +118,7 @@
SecureSettings secureSettings,
@Main Executor uiExecutor,
DumpManager dumpManager,
- ClockEventController clockEventController,
- FeatureFlags featureFlags) {
+ ClockEventController clockEventController) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
mClockRegistry = clockRegistry;
@@ -134,7 +131,6 @@
mDumpManager = dumpManager;
mClockEventController = clockEventController;
- mClockRegistry.setEnabled(featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS));
mClockChangedListener = () -> {
setClock(mClockRegistry.createCurrentClock());
};
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index aff9dcb..cc1f2fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -44,7 +44,6 @@
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_DREAM_STOPPED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_DURING_CANCELLATION;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET;
@@ -287,6 +286,7 @@
}
}
};
+ private final FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
HashMap<Integer, SimData> mSimDatas = new HashMap<>();
HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
@@ -1807,11 +1807,21 @@
}
}
- protected void handleStartedWakingUp() {
+ protected void handleStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) {
Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
Assert.isMainThread();
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_STARTED_WAKING_UP);
- requestActiveUnlock(ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE, "wakingUp");
+
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+ if (mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason)) {
+ FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason);
+ updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_UPDATED_STARTED_WAKING_UP);
+ requestActiveUnlock(ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE, "wakingUp - "
+ + PowerManager.wakeReasonToString(pmWakeReason));
+ } else {
+ mLogger.logSkipUpdateFaceListeningOnWakeup(pmWakeReason);
+ }
+
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1863,12 +1873,9 @@
cb.onDreamingStateChanged(mIsDreaming);
}
}
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
if (mIsDreaming) {
- updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
updateFaceListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_STOPPED_DREAM_STARTED);
- } else {
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_TRIGGERED_DREAM_STOPPED);
}
}
@@ -1948,7 +1955,8 @@
PackageManager packageManager,
@Nullable FaceManager faceManager,
@Nullable FingerprintManager fingerprintManager,
- @Nullable BiometricManager biometricManager) {
+ @Nullable BiometricManager biometricManager,
+ FaceWakeUpTriggersConfig faceWakeUpTriggersConfig) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mTelephonyListenerManager = telephonyListenerManager;
@@ -1987,6 +1995,7 @@
R.array.config_face_acquire_device_entry_ignorelist))
.boxed()
.collect(Collectors.toSet());
+ mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig;
mHandler = new Handler(mainLooper) {
@Override
@@ -2036,7 +2045,7 @@
break;
case MSG_STARTED_WAKING_UP:
Trace.beginSection("KeyguardUpdateMonitor#handler MSG_STARTED_WAKING_UP");
- handleStartedWakingUp();
+ handleStartedWakingUp(msg.arg1);
Trace.endSection();
break;
case MSG_SIM_SUBSCRIPTION_INFO_CHANGED:
@@ -2227,8 +2236,8 @@
private void updateFaceEnrolled(int userId) {
mIsFaceEnrolled = whitelistIpcs(
() -> mFaceManager != null && mFaceManager.isHardwareDetected()
- && mFaceManager.hasEnrolledTemplates(userId)
- && mBiometricEnabledForUser.get(userId));
+ && mBiometricEnabledForUser.get(userId))
+ && mAuthController.isFaceAuthEnrolled(userId);
}
public boolean isFaceSupported() {
@@ -2784,8 +2793,14 @@
// Waiting for ERROR_CANCELED before requesting auth again
return;
}
- mLogger.logStartedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason());
- mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId());
+ mLogger.logStartedListeningForFace(mFaceRunningState, faceAuthUiEvent);
+ mUiEventLogger.logWithInstanceIdAndPosition(
+ faceAuthUiEvent,
+ 0,
+ null,
+ getKeyguardSessionId(),
+ faceAuthUiEvent.getExtraInfo()
+ );
if (unlockPossible) {
mFaceCancelSignal = new CancellationSignal();
@@ -3564,11 +3579,16 @@
// TODO: use these callbacks elsewhere in place of the existing notifyScreen*()
// (KeyguardViewMediator, KeyguardHostView)
- public void dispatchStartedWakingUp() {
+ /**
+ * Dispatch wakeup events to:
+ * - update biometric listening states
+ * - send to registered KeyguardUpdateMonitorCallbacks
+ */
+ public void dispatchStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) {
synchronized (this) {
mDeviceInteractive = true;
}
- mHandler.sendEmptyMessage(MSG_STARTED_WAKING_UP);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_STARTED_WAKING_UP, pmWakeReason, 0));
}
public void dispatchStartedGoingToSleep(int why) {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 70758df..8fbbd38 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -24,6 +24,8 @@
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -46,6 +48,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import com.android.systemui.Dumpable;
@@ -55,6 +58,10 @@
import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
@@ -67,6 +74,7 @@
import java.io.PrintWriter;
import java.util.Objects;
+import java.util.function.Consumer;
import javax.inject.Inject;
@@ -103,6 +111,9 @@
@NonNull private CharSequence mLockedLabel;
@NonNull private final VibratorHelper mVibrator;
@Nullable private final AuthRippleController mAuthRippleController;
+ @NonNull private final FeatureFlags mFeatureFlags;
+ @NonNull private final KeyguardTransitionInteractor mTransitionInteractor;
+ @NonNull private final KeyguardInteractor mKeyguardInteractor;
// Tracks the velocity of a touch to help filter out the touches that move too fast.
private VelocityTracker mVelocityTracker;
@@ -139,6 +150,20 @@
private boolean mDownDetected;
private final Rect mSensorTouchLocation = new Rect();
+ @VisibleForTesting
+ final Consumer<TransitionStep> mDozeTransitionCallback = (TransitionStep step) -> {
+ mInterpolatedDarkAmount = step.getValue();
+ mView.setDozeAmount(step.getValue());
+ updateBurnInOffsets();
+ };
+
+ @VisibleForTesting
+ final Consumer<Boolean> mIsDozingCallback = (Boolean isDozing) -> {
+ mIsDozing = isDozing;
+ updateBurnInOffsets();
+ updateVisibility();
+ };
+
@Inject
public LockIconViewController(
@Nullable LockIconView view,
@@ -154,7 +179,10 @@
@NonNull @Main DelayableExecutor executor,
@NonNull VibratorHelper vibrator,
@Nullable AuthRippleController authRippleController,
- @NonNull @Main Resources resources
+ @NonNull @Main Resources resources,
+ @NonNull KeyguardTransitionInteractor transitionInteractor,
+ @NonNull KeyguardInteractor keyguardInteractor,
+ @NonNull FeatureFlags featureFlags
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -168,6 +196,9 @@
mExecutor = executor;
mVibrator = vibrator;
mAuthRippleController = authRippleController;
+ mTransitionInteractor = transitionInteractor;
+ mKeyguardInteractor = keyguardInteractor;
+ mFeatureFlags = featureFlags;
mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
@@ -184,6 +215,12 @@
@Override
protected void onInit() {
mView.setAccessibilityDelegate(mAccessibilityDelegate);
+
+ if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ collectFlow(mView, mTransitionInteractor.getDozeAmountTransition(),
+ mDozeTransitionCallback);
+ collectFlow(mView, mKeyguardInteractor.isDozing(), mIsDozingCallback);
+ }
}
@Override
@@ -379,14 +416,17 @@
pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
pw.println(" mShowLockIcon: " + mShowLockIcon);
pw.println(" mShowAodUnlockedIcon: " + mShowAodUnlockedIcon);
- pw.println(" mIsDozing: " + mIsDozing);
- pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
- pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
- pw.println(" mRunningFPS: " + mRunningFPS);
- pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
- pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
- pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
- pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
+ pw.println();
+ pw.println(" mIsDozing: " + mIsDozing);
+ pw.println(" isFlagEnabled(DOZING_MIGRATION_1): "
+ + mFeatureFlags.isEnabled(DOZING_MIGRATION_1));
+ pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
+ pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
+ pw.println(" mRunningFPS: " + mRunningFPS);
+ pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
+ pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
+ pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
+ pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
if (mView != null) {
mView.dump(pw, args);
@@ -427,16 +467,20 @@
new StatusBarStateController.StateListener() {
@Override
public void onDozeAmountChanged(float linear, float eased) {
- mInterpolatedDarkAmount = eased;
- mView.setDozeAmount(eased);
- updateBurnInOffsets();
+ if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ mInterpolatedDarkAmount = eased;
+ mView.setDozeAmount(eased);
+ updateBurnInOffsets();
+ }
}
@Override
public void onDozingChanged(boolean isDozing) {
- mIsDozing = isDozing;
- updateBurnInOffsets();
- updateVisibility();
+ if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ mIsDozing = isDozing;
+ updateBurnInOffsets();
+ updateVisibility();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index f43f559..9767313 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -18,10 +18,13 @@
import android.content.Context;
import android.os.Handler;
+import android.os.UserHandle;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.shared.clocks.DefaultClockProvider;
import com.android.systemui.shared.plugins.PluginManager;
@@ -39,7 +42,14 @@
@Application Context context,
PluginManager pluginManager,
@Main Handler handler,
- DefaultClockProvider defaultClockProvider) {
- return new ClockRegistry(context, pluginManager, handler, defaultClockProvider);
+ DefaultClockProvider defaultClockProvider,
+ FeatureFlags featureFlags) {
+ return new ClockRegistry(
+ context,
+ pluginManager,
+ handler,
+ featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
+ UserHandle.USER_ALL,
+ defaultClockProvider);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 2f79e30..31fc320 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -17,9 +17,12 @@
package com.android.keyguard.logging
import android.hardware.biometrics.BiometricConstants.LockoutMode
+import android.os.PowerManager
+import android.os.PowerManager.WakeReason
import android.telephony.ServiceState
import android.telephony.SubscriptionInfo
import com.android.keyguard.ActiveUnlockConfig
+import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.KeyguardListenModel
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.plugins.log.LogBuffer
@@ -269,11 +272,19 @@
logBuffer.log(TAG, VERBOSE, { int1 = subId }, { "reportSimUnlocked(subId=$int1)" })
}
- fun logStartedListeningForFace(faceRunningState: Int, faceAuthReason: String) {
+ fun logStartedListeningForFace(faceRunningState: Int, faceAuthUiEvent: FaceAuthUiEvent) {
logBuffer.log(TAG, VERBOSE, {
int1 = faceRunningState
- str1 = faceAuthReason
- }, { "startListeningForFace(): $int1, reason: $str1" })
+ str1 = faceAuthUiEvent.reason
+ str2 = faceAuthUiEvent.extraInfoToString()
+ }, { "startListeningForFace(): $int1, reason: $str1 $str2" })
+ }
+
+ fun logStartedListeningForFaceFromWakeUp(faceRunningState: Int, @WakeReason pmWakeReason: Int) {
+ logBuffer.log(TAG, VERBOSE, {
+ int1 = faceRunningState
+ str1 = PowerManager.wakeReasonToString(pmWakeReason)
+ }, { "startListeningForFace(): $int1, reason: wakeUp-$str1" })
}
fun logStoppedListeningForFace(faceRunningState: Int, faceAuthReason: String) {
@@ -383,4 +394,10 @@
}, { "#update secure=$bool1 canDismissKeyguard=$bool2" +
" trusted=$bool3 trustManaged=$bool4" })
}
+
+ fun logSkipUpdateFaceListeningOnWakeup(@WakeReason pmWakeReason: Int) {
+ logBuffer.log(TAG, VERBOSE, {
+ str1 = PowerManager.wakeReasonToString(pmWakeReason)
+ }, { "Skip updating face listening state on wakeup from $str1"})
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 9f1c9b4..0a2dc5b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -597,6 +597,7 @@
case INTENT_ACTION_DPAD_CENTER: {
Intent intent = new Intent(intentAction);
intent.setPackage(context.getPackageName());
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
return PendingIntent.getBroadcast(context, 0, intent,
PendingIntent.FLAG_IMMUTABLE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index ea334b2..777d10c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -28,6 +28,7 @@
import android.text.TextUtils;
import android.view.Display;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.annotation.MainThread;
@@ -56,6 +57,7 @@
private Context mContext;
private final WindowManager mWindowManager;
private final DisplayManager mDisplayManager;
+ private final AccessibilityManager mAccessibilityManager;
private final FeatureFlags mFeatureFlags;
@VisibleForTesting
IAccessibilityFloatingMenu mFloatingMenu;
@@ -96,6 +98,7 @@
public AccessibilityFloatingMenuController(Context context,
WindowManager windowManager,
DisplayManager displayManager,
+ AccessibilityManager accessibilityManager,
AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
AccessibilityButtonModeObserver accessibilityButtonModeObserver,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -103,6 +106,7 @@
mContext = context;
mWindowManager = windowManager;
mDisplayManager = displayManager;
+ mAccessibilityManager = accessibilityManager;
mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -180,7 +184,8 @@
final Display defaultDisplay = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
mFloatingMenu = new MenuViewLayerController(
mContext.createWindowContext(defaultDisplay,
- TYPE_NAVIGATION_BAR_PANEL, /* options= */ null), mWindowManager);
+ TYPE_NAVIGATION_BAR_PANEL, /* options= */ null), mWindowManager,
+ mAccessibilityManager);
} else {
mFloatingMenu = new AccessibilityFloatingMenu(mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
new file mode 100644
index 0000000..ee048e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.floatingmenu;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.ComponentCallbacks;
+import android.content.res.Configuration;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.systemui.R;
+import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+
+/**
+ * Controls the interaction between {@link MagnetizedObject} and
+ * {@link MagnetizedObject.MagneticTarget}.
+ */
+class DismissAnimationController implements ComponentCallbacks {
+ private static final float COMPLETELY_OPAQUE = 1.0f;
+ private static final float COMPLETELY_TRANSPARENT = 0.0f;
+ private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f;
+ private static final float ANIMATING_MAX_ALPHA = 0.7f;
+
+ private final DismissView mDismissView;
+ private final MenuView mMenuView;
+ private final ValueAnimator mDismissAnimator;
+ private final MagnetizedObject<?> mMagnetizedObject;
+ private float mMinDismissSize;
+ private float mSizePercent;
+
+ DismissAnimationController(DismissView dismissView, MenuView menuView) {
+ mDismissView = dismissView;
+ mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
+ mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
+ mMenuView = menuView;
+
+ updateResources();
+
+ mDismissAnimator = ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
+ mDismissAnimator.addUpdateListener(dismissAnimation -> {
+ final float animatedValue = (float) dismissAnimation.getAnimatedValue();
+ final float scaleValue = Math.max(animatedValue, mSizePercent);
+ dismissView.getCircle().setScaleX(scaleValue);
+ dismissView.getCircle().setScaleY(scaleValue);
+
+ menuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
+ });
+
+ mDismissAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ super.onAnimationEnd(animation, isReverse);
+
+ if (isReverse) {
+ mDismissView.getCircle().setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
+ mDismissView.getCircle().setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
+ mMenuView.setAlpha(COMPLETELY_OPAQUE);
+ }
+ }
+ });
+
+ mMagnetizedObject =
+ new MagnetizedObject<MenuView>(mMenuView.getContext(), mMenuView,
+ new MenuAnimationController.MenuPositionProperty(
+ DynamicAnimation.TRANSLATION_X),
+ new MenuAnimationController.MenuPositionProperty(
+ DynamicAnimation.TRANSLATION_Y)) {
+ @Override
+ public void getLocationOnScreen(MenuView underlyingObject, int[] loc) {
+ underlyingObject.getLocationOnScreen(loc);
+ }
+
+ @Override
+ public float getHeight(MenuView underlyingObject) {
+ return underlyingObject.getHeight();
+ }
+
+ @Override
+ public float getWidth(MenuView underlyingObject) {
+ return underlyingObject.getWidth();
+ }
+ };
+
+ final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget(
+ dismissView.getCircle(), (int) mMinDismissSize);
+ mMagnetizedObject.addTarget(magneticTarget);
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ updateResources();
+ }
+
+ @Override
+ public void onLowMemory() {
+ // Do nothing
+ }
+
+ void showDismissView(boolean show) {
+ if (show) {
+ mDismissView.show();
+ } else {
+ mDismissView.hide();
+ }
+ }
+
+ void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) {
+ mMagnetizedObject.setMagnetListener(magnetListener);
+ }
+
+ void maybeConsumeDownMotionEvent(MotionEvent event) {
+ mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
+ /**
+ * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized object to check if it was
+ * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
+ *
+ * @param event that move the magnetized object which is also the menu list view.
+ * @return true if the location of the motion events moves within the magnetic field of a
+ * target, but false if didn't set
+ * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ */
+ boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
+ return mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
+ /**
+ * This used to pass {@link MotionEvent#ACTION_UP} to the magnetized object to check if it was
+ * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
+ *
+ * @param event that move the magnetized object which is also the menu list view.
+ * @return true if the location of the motion events moves within the magnetic field of a
+ * target, but false if didn't set
+ * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ */
+ boolean maybeConsumeUpMotionEvent(MotionEvent event) {
+ return mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
+ void animateDismissMenu(boolean scaleUp) {
+ if (scaleUp) {
+ mDismissAnimator.start();
+ } else {
+ mDismissAnimator.reverse();
+ }
+ }
+
+ private void updateResources() {
+ final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize(
+ R.dimen.dismiss_circle_size);
+ mMinDismissSize = mDismissView.getResources().getDimensionPixelSize(
+ R.dimen.dismiss_circle_small);
+ mSizePercent = mMinDismissSize / maxDismissSize;
+ }
+
+ interface DismissCallback {
+ void onDismiss();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index d6d03990..396f584 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -35,6 +35,8 @@
import androidx.dynamicanimation.animation.SpringForce;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.util.Preconditions;
+
import java.util.HashMap;
/**
@@ -47,6 +49,9 @@
private static final float MIN_PERCENT = 0.0f;
private static final float MAX_PERCENT = 1.0f;
private static final float COMPLETELY_OPAQUE = 1.0f;
+ private static final float COMPLETELY_TRANSPARENT = 0.0f;
+ private static final float SCALE_SHRINK = 0.0f;
+ private static final float SCALE_GROW = 1.0f;
private static final float FLING_FRICTION_SCALAR = 1.9f;
private static final float DEFAULT_FRICTION = 4.2f;
private static final float SPRING_AFTER_FLING_DAMPING_RATIO = 0.85f;
@@ -61,6 +66,7 @@
private final Handler mHandler;
private boolean mIsMovedToEdge;
private boolean mIsFadeEffectEnabled;
+ private DismissAnimationController.DismissCallback mDismissCallback;
// Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
// DynamicAnimation.TRANSLATION_Y} to be well controlled by the touch handler
@@ -99,6 +105,11 @@
}
}
+ void setDismissCallback(
+ DismissAnimationController.DismissCallback dismissCallback) {
+ mDismissCallback = dismissCallback;
+ }
+
void moveToTopLeftPosition() {
mIsMovedToEdge = false;
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
@@ -129,6 +140,13 @@
constrainPositionAndUpdate(position);
}
+ void removeMenu() {
+ Preconditions.checkArgument(mDismissCallback != null,
+ "The dismiss callback should be initialized first.");
+
+ mDismissCallback.onDismiss();
+ }
+
void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) {
final boolean shouldMenuFlingLeft = isOnLeftSide()
? velocityX < ESCAPE_VELOCITY
@@ -297,6 +315,28 @@
mMenuView.onDraggingStart();
}
+ void startShrinkAnimation(Runnable endAction) {
+ mMenuView.animate().cancel();
+
+ mMenuView.animate()
+ .scaleX(SCALE_SHRINK)
+ .scaleY(SCALE_SHRINK)
+ .alpha(COMPLETELY_TRANSPARENT)
+ .translationY(mMenuView.getTranslationY())
+ .withEndAction(endAction).start();
+ }
+
+ void startGrowAnimation() {
+ mMenuView.animate().cancel();
+
+ mMenuView.animate()
+ .scaleX(SCALE_GROW)
+ .scaleY(SCALE_GROW)
+ .alpha(COMPLETELY_OPAQUE)
+ .translationY(mMenuView.getTranslationY())
+ .start();
+ }
+
private void onSpringAnimationEnd(PointF position) {
mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
constrainPositionAndUpdate(position);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
index e69a248..ac5736b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
@@ -84,6 +84,12 @@
new AccessibilityNodeInfoCompat.AccessibilityActionCompat(moveEdgeId,
res.getString(moveEdgeTextResId));
info.addAction(moveToOrOutEdge);
+
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat removeMenu =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.action_remove_menu,
+ res.getString(R.string.accessibility_floating_button_action_remove_menu));
+ info.addAction(removeMenu);
}
@Override
@@ -126,6 +132,11 @@
return true;
}
+ if (action == R.id.action_remove_menu) {
+ mAnimationController.removeMenu();
+ return true;
+ }
+
return super.performAccessibilityAction(host, action, args);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index 3146c9f..bc3cf0a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -38,9 +38,12 @@
private final PointF mMenuTranslationDown = new PointF();
private boolean mIsDragging = false;
private float mTouchSlop;
+ private final DismissAnimationController mDismissAnimationController;
- MenuListViewTouchHandler(MenuAnimationController menuAnimationController) {
+ MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
+ DismissAnimationController dismissAnimationController) {
mMenuAnimationController = menuAnimationController;
+ mDismissAnimationController = dismissAnimationController;
}
@Override
@@ -61,6 +64,7 @@
mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
mMenuAnimationController.cancelAnimations();
+ mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
break;
case MotionEvent.ACTION_MOVE:
if (mIsDragging || Math.hypot(dx, dy) > mTouchSlop) {
@@ -69,8 +73,13 @@
mMenuAnimationController.onDraggingStart();
}
- mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
- mMenuAnimationController.moveToPositionYIfNeeded(mMenuTranslationDown.y + dy);
+ mDismissAnimationController.showDismissView(/* show= */ true);
+
+ if (!mDismissAnimationController.maybeConsumeMoveMotionEvent(motionEvent)) {
+ mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
+ mMenuAnimationController.moveToPositionYIfNeeded(
+ mMenuTranslationDown.y + dy);
+ }
}
break;
case MotionEvent.ACTION_UP:
@@ -79,10 +88,18 @@
final float endX = mMenuTranslationDown.x + dx;
mIsDragging = false;
- if (!mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
+ if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
+ mDismissAnimationController.showDismissView(/* show= */ false);
+ mMenuAnimationController.fadeOutIfEnabled();
+
+ return true;
+ }
+
+ if (!mDismissAnimationController.maybeConsumeUpMotionEvent(motionEvent)) {
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
mMenuAnimationController.flingMenuThenSpringToEdge(endX,
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+ mDismissAnimationController.showDismissView(/* show= */ false);
}
// Avoid triggering the listener of the item.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
new file mode 100644
index 0000000..9875ad0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.floatingmenu;
+
+import static android.util.TypedValue.COMPLEX_UNIT_PX;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The message view with the action prompt to whether to undo operation for users when removing
+ * the {@link MenuView}.
+ */
+class MenuMessageView extends LinearLayout implements
+ ViewTreeObserver.OnComputeInternalInsetsListener {
+ private final TextView mTextView;
+ private final Button mUndoButton;
+
+ @IntDef({
+ Index.TEXT_VIEW,
+ Index.UNDO_BUTTON
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Index {
+ int TEXT_VIEW = 0;
+ int UNDO_BUTTON = 1;
+ }
+
+ MenuMessageView(Context context) {
+ super(context);
+
+ setVisibility(GONE);
+
+ mTextView = new TextView(context);
+ mUndoButton = new Button(context);
+
+ addView(mTextView, Index.TEXT_VIEW,
+ new LayoutParams(/* width= */ 0, WRAP_CONTENT, /* weight= */ 1));
+ addView(mUndoButton, Index.UNDO_BUTTON, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ updateResources();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ final FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams(WRAP_CONTENT,
+ WRAP_CONTENT);
+ containerParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+ setLayoutParams(containerParams);
+ setGravity(Gravity.CENTER_VERTICAL);
+
+ mUndoButton.setBackground(null);
+
+ updateResources();
+
+ getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ }
+
+ @Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+ inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+
+ if (getVisibility() == VISIBLE) {
+ final int x = (int) getX();
+ final int y = (int) getY();
+ inoutInfo.touchableRegion.union(new Rect(x, y, x + getWidth(), y + getHeight()));
+ }
+ }
+
+ /**
+ * Registers a listener to be invoked when this undo action button is clicked. It should be
+ * called after {@link View#onAttachedToWindow()}.
+ *
+ * @param listener The listener that will run
+ */
+ void setUndoListener(OnClickListener listener) {
+ mUndoButton.setOnClickListener(listener);
+ }
+
+ private void updateResources() {
+ final Resources res = getResources();
+
+ final int containerPadding =
+ res.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_message_container_horizontal_padding);
+ final int margin = res.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_message_margin);
+ final FrameLayout.LayoutParams containerParams =
+ (FrameLayout.LayoutParams) getLayoutParams();
+ containerParams.setMargins(margin, margin, margin, margin);
+ setLayoutParams(containerParams);
+ setBackground(res.getDrawable(R.drawable.accessibility_floating_message_background));
+ setPadding(containerPadding, /* top= */ 0, containerPadding, /* bottom= */ 0);
+ setMinimumWidth(
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_min_width));
+ setMinimumHeight(
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_min_height));
+ setElevation(
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_elevation));
+
+ final int textPadding =
+ res.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_message_text_vertical_padding);
+ final int textColor = res.getColor(R.color.accessibility_floating_menu_message_text);
+ final int textSize = res.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_message_text_size);
+ mTextView.setPadding(/* left= */ 0, textPadding, /* right= */ 0, textPadding);
+ mTextView.setTextSize(COMPLEX_UNIT_PX, textSize);
+ mTextView.setTextColor(textColor);
+
+ final ColorStateList colorAccent = Utils.getColorAccent(getContext());
+ mUndoButton.setText(res.getString(R.string.accessibility_floating_button_undo));
+ mUndoButton.setTextSize(COMPLEX_UNIT_PX, textSize);
+ mUndoButton.setTextColor(colorAccent);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 15d139c..6a14af5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -42,7 +42,7 @@
import java.util.List;
/**
- * The menu view displays the accessibility features.
+ * The container view displays the accessibility features.
*/
@SuppressLint("ViewConstructor")
class MenuView extends FrameLayout implements
@@ -64,13 +64,14 @@
this::onTargetFeaturesChanged;
private final MenuViewAppearance mMenuViewAppearance;
+ private OnTargetFeaturesChangeListener mFeaturesChangeListener;
+
MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
super(context);
mMenuViewModel = menuViewModel;
mMenuViewAppearance = menuViewAppearance;
mMenuAnimationController = new MenuAnimationController(this);
-
mAdapter = new AccessibilityTargetAdapter(mTargetFeatures);
mTargetFeaturesView = new RecyclerView(context);
mTargetFeaturesView.setAdapter(mAdapter);
@@ -96,7 +97,9 @@
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- inoutInfo.touchableRegion.set(mBoundsInParent);
+ if (getVisibility() == VISIBLE) {
+ inoutInfo.touchableRegion.union(mBoundsInParent);
+ }
}
@Override
@@ -108,10 +111,18 @@
mTargetFeaturesView.setOverScrollMode(mMenuViewAppearance.getMenuScrollMode());
}
+ void setOnTargetFeaturesChangeListener(OnTargetFeaturesChangeListener listener) {
+ mFeaturesChangeListener = listener;
+ }
+
void addOnItemTouchListenerToList(RecyclerView.OnItemTouchListener listener) {
mTargetFeaturesView.addOnItemTouchListener(listener);
}
+ MenuAnimationController getMenuAnimationController() {
+ return mMenuAnimationController;
+ }
+
@SuppressLint("NotifyDataSetChanged")
private void onItemSizeChanged() {
mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding());
@@ -139,7 +150,7 @@
onEdgeChanged();
}
- private void onEdgeChanged() {
+ void onEdgeChanged() {
final int[] insets = mMenuViewAppearance.getMenuInsets();
getContainerViewInsetLayer().setLayerInset(INDEX_MENU_ITEM, insets[0], insets[1], insets[2],
insets[3]);
@@ -193,6 +204,9 @@
onEdgeChanged();
onPositionChanged();
+ if (mFeaturesChangeListener != null) {
+ mFeaturesChangeListener.onChange(newTargetFeatures);
+ }
mMenuAnimationController.fadeOutIfEnabled();
}
@@ -299,4 +313,17 @@
final ViewGroup parentView = (ViewGroup) getParent();
parentView.setSystemGestureExclusionRects(Collections.singletonList(mBoundsInParent));
}
+
+ /**
+ * Interface definition for the {@link AccessibilityTarget} list changes.
+ */
+ interface OnTargetFeaturesChangeListener {
+ /**
+ * Called when the list of accessibility target features was updated. This will be
+ * invoked when the end of {@code onTargetFeaturesChanged}.
+ *
+ * @param newTargetFeatures the list related to the current accessibility features.
+ */
+ void onChange(List<AccessibilityTarget> newTargetFeatures);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 5252519..33e155d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -16,42 +16,155 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
+
import android.annotation.IntDef;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.PluralsMessageFormatter;
import android.view.MotionEvent;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
+import android.widget.TextView;
import androidx.annotation.NonNull;
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
- * The basic interactions with the child view {@link MenuView}.
+ * The basic interactions with the child views {@link MenuView}, {@link DismissView}, and
+ * {@link MenuMessageView}. When dragging the menu view, the dismissed view would be shown at the
+ * same time. If the menu view overlaps on the dismissed circle view and drops out, the menu
+ * message view would be shown and allowed users to undo it.
*/
@SuppressLint("ViewConstructor")
class MenuViewLayer extends FrameLayout {
+ private static final int SHOW_MESSAGE_DELAY_MS = 3000;
+
private final MenuView mMenuView;
+ private final MenuMessageView mMessageView;
+ private final DismissView mDismissView;
+ private final MenuAnimationController mMenuAnimationController;
+ private final AccessibilityManager mAccessibilityManager;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final IAccessibilityFloatingMenu mFloatingMenu;
+ private final DismissAnimationController mDismissAnimationController;
@IntDef({
- LayerIndex.MENU_VIEW
+ LayerIndex.MENU_VIEW,
+ LayerIndex.DISMISS_VIEW,
+ LayerIndex.MESSAGE_VIEW,
})
@Retention(RetentionPolicy.SOURCE)
@interface LayerIndex {
int MENU_VIEW = 0;
+ int DISMISS_VIEW = 1;
+ int MESSAGE_VIEW = 2;
}
- MenuViewLayer(@NonNull Context context, WindowManager windowManager) {
+ @VisibleForTesting
+ final Runnable mDismissMenuAction = new Runnable() {
+ @Override
+ public void run() {
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "");
+ mFloatingMenu.hide();
+ }
+ };
+
+ MenuViewLayer(@NonNull Context context, WindowManager windowManager,
+ AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu) {
super(context);
+ mAccessibilityManager = accessibilityManager;
+ mFloatingMenu = floatingMenu;
+
final MenuViewModel menuViewModel = new MenuViewModel(context);
final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context,
windowManager);
mMenuView = new MenuView(context, menuViewModel, menuViewAppearance);
+ mMenuAnimationController = mMenuView.getMenuAnimationController();
+ mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
+
+ mDismissView = new DismissView(context);
+ mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
+ mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ mDismissAnimationController.animateDismissMenu(/* scaleUp= */ true);
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velocityX, float velocityY, boolean wasFlungOut) {
+ mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ hideMenuAndShowMessage();
+ mDismissView.hide();
+ mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ }
+ });
+
+ final MenuListViewTouchHandler menuListViewTouchHandler = new MenuListViewTouchHandler(
+ mMenuAnimationController, mDismissAnimationController);
+ mMenuView.addOnItemTouchListenerToList(menuListViewTouchHandler);
+
+ mMessageView = new MenuMessageView(context);
+
+ mMenuView.setOnTargetFeaturesChangeListener(newTargetFeatures -> {
+ if (newTargetFeatures.size() < 1) {
+ return;
+ }
+
+ // During the undo action period, the pending action will be canceled and undo back
+ // to the previous state if users did any action related to the accessibility features.
+ if (mMessageView.getVisibility() == VISIBLE) {
+ undo();
+ }
+
+ final TextView messageText = (TextView) mMessageView.getChildAt(Index.TEXT_VIEW);
+ messageText.setText(getMessageText(newTargetFeatures));
+ });
addView(mMenuView, LayerIndex.MENU_VIEW);
+ addView(mDismissView, LayerIndex.DISMISS_VIEW);
+ addView(mMessageView, LayerIndex.MESSAGE_VIEW);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDismissView.updateResources();
+ }
+
+ private String getMessageText(List<AccessibilityTarget> newTargetFeatures) {
+ Preconditions.checkArgument(newTargetFeatures.size() > 0,
+ "The list should at least have one feature.");
+
+ final Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", newTargetFeatures.size());
+ arguments.put("label", newTargetFeatures.get(0).getLabel());
+ return PluralsMessageFormatter.format(getResources(), arguments,
+ R.string.accessibility_floating_button_undo_message_text);
}
@Override
@@ -68,6 +181,8 @@
super.onAttachedToWindow();
mMenuView.show();
+ mMessageView.setUndoListener(view -> undo());
+ mContext.registerComponentCallbacks(mDismissAnimationController);
}
@Override
@@ -75,5 +190,26 @@
super.onDetachedFromWindow();
mMenuView.hide();
+ mHandler.removeCallbacksAndMessages(/* token= */ null);
+ mContext.unregisterComponentCallbacks(mDismissAnimationController);
+ }
+
+ private void hideMenuAndShowMessage() {
+ final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
+ SHOW_MESSAGE_DELAY_MS,
+ AccessibilityManager.FLAG_CONTENT_TEXT
+ | AccessibilityManager.FLAG_CONTENT_CONTROLS);
+ mHandler.postDelayed(mDismissMenuAction, delayTime);
+ mMessageView.setVisibility(VISIBLE);
+ mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
+ }
+
+ private void undo() {
+ mHandler.removeCallbacksAndMessages(/* token= */ null);
+ mMessageView.setVisibility(GONE);
+ mMenuView.onEdgeChanged();
+ mMenuView.onPositionChanged();
+ mMenuView.setVisibility(VISIBLE);
+ mMenuAnimationController.startGrowAnimation();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index d2093c2..b1a64ed 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -22,6 +22,7 @@
import android.graphics.PixelFormat;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
/**
* Controls the {@link MenuViewLayer} whether to be attached to the window via the interface
@@ -32,9 +33,10 @@
private final MenuViewLayer mMenuViewLayer;
private boolean mIsShowing;
- MenuViewLayerController(Context context, WindowManager windowManager) {
+ MenuViewLayerController(Context context, WindowManager windowManager,
+ AccessibilityManager accessibilityManager) {
mWindowManager = windowManager;
- mMenuViewLayer = new MenuViewLayer(context, windowManager);
+ mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 3e796cd0..f74c721 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -26,6 +26,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AlertDialog;
import android.content.Context;
import android.graphics.PixelFormat;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
@@ -63,6 +64,9 @@
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
+import com.android.systemui.biometrics.ui.CredentialView;
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -74,11 +78,13 @@
import java.util.List;
import java.util.Set;
+import javax.inject.Provider;
+
/**
* Top level container/controller for the BiometricPrompt UI.
*/
public class AuthContainerView extends LinearLayout
- implements AuthDialog, WakefulnessLifecycle.Observer {
+ implements AuthDialog, WakefulnessLifecycle.Observer, CredentialView.Host {
private static final String TAG = "AuthContainerView";
@@ -112,22 +118,25 @@
private final IBinder mWindowToken = new Binder();
private final WindowManager mWindowManager;
private final Interpolator mLinearOutSlowIn;
- private final CredentialCallback mCredentialCallback;
private final LockPatternUtils mLockPatternUtils;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final InteractionJankMonitor mInteractionJankMonitor;
+ // TODO: these should be migrated out once ready
+ private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
+ private final Provider<CredentialViewModel> mCredentialViewModelProvider;
+
@VisibleForTesting final BiometricCallback mBiometricCallback;
@Nullable private AuthBiometricView mBiometricView;
- @Nullable private AuthCredentialView mCredentialView;
+ @Nullable private View mCredentialView;
private final AuthPanelController mPanelController;
private final FrameLayout mFrameLayout;
private final ImageView mBackgroundView;
private final ScrollView mBiometricScrollView;
private final View mPanelView;
private final float mTranslationY;
- @ContainerState private int mContainerState = STATE_UNKNOWN;
+ @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
private final Set<Integer> mFailedModalities = new HashSet<Integer>();
private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
@@ -229,11 +238,13 @@
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
@NonNull UserManager userManager,
@NonNull LockPatternUtils lockPatternUtils,
- @NonNull InteractionJankMonitor jankMonitor) {
+ @NonNull InteractionJankMonitor jankMonitor,
+ @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+ @NonNull Provider<CredentialViewModel> credentialViewModelProvider) {
mConfig.mSensorIds = sensorIds;
return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
- userManager, lockPatternUtils, jankMonitor, new Handler(Looper.getMainLooper()),
- bgExecutor);
+ userManager, lockPatternUtils, jankMonitor, biometricPromptInteractor,
+ credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor);
}
}
@@ -271,14 +282,51 @@
}
}
- final class CredentialCallback implements AuthCredentialView.Callback {
- @Override
- public void onCredentialMatched(byte[] attestation) {
- mCredentialAttestation = attestation;
- animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
+ @Override
+ public void onCredentialMatched(@NonNull byte[] attestation) {
+ mCredentialAttestation = attestation;
+ animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
+ }
+
+ @Override
+ public void onCredentialAborted() {
+ sendEarlyUserCanceled();
+ animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ }
+
+ @Override
+ public void onCredentialAttemptsRemaining(int remaining, @NonNull String messageBody) {
+ // Only show dialog if <=1 attempts are left before wiping.
+ if (remaining == 1) {
+ showLastAttemptBeforeWipeDialog(messageBody);
+ } else if (remaining <= 0) {
+ showNowWipingDialog(messageBody);
}
}
+ private void showLastAttemptBeforeWipeDialog(@NonNull String messageBody) {
+ final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
+ .setTitle(R.string.biometric_dialog_last_attempt_before_wipe_dialog_title)
+ .setMessage(messageBody)
+ .setPositiveButton(android.R.string.ok, null)
+ .create();
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.show();
+ }
+
+ private void showNowWipingDialog(@NonNull String messageBody) {
+ final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
+ .setMessage(messageBody)
+ .setPositiveButton(
+ com.android.settingslib.R.string.failed_attempts_now_wiping_dialog_dismiss,
+ null /* OnClickListener */)
+ .setOnDismissListener(
+ dialog -> animateAway(AuthDialogCallback.DISMISSED_ERROR))
+ .create();
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.show();
+ }
+
@VisibleForTesting
AuthContainerView(Config config,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
@@ -287,6 +335,8 @@
@NonNull UserManager userManager,
@NonNull LockPatternUtils lockPatternUtils,
@NonNull InteractionJankMonitor jankMonitor,
+ @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+ @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull Handler mainHandler,
@NonNull @Background DelayableExecutor bgExecutor) {
super(config.mContext);
@@ -302,7 +352,6 @@
.getDimension(R.dimen.biometric_dialog_animation_translation_offset);
mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
mBiometricCallback = new BiometricCallback();
- mCredentialCallback = new CredentialCallback();
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
mFrameLayout = (FrameLayout) layoutInflater.inflate(
@@ -314,6 +363,8 @@
mPanelController = new AuthPanelController(mContext, mPanelView);
mBackgroundExecutor = bgExecutor;
mInteractionJankMonitor = jankMonitor;
+ mBiometricPromptInteractor = biometricPromptInteractor;
+ mCredentialViewModelProvider = credentialViewModelProvider;
// Inflate biometric view only if necessary.
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
@@ -404,12 +455,12 @@
switch (credentialType) {
case Utils.CREDENTIAL_PATTERN:
- mCredentialView = (AuthCredentialView) factory.inflate(
+ mCredentialView = factory.inflate(
R.layout.auth_credential_pattern_view, null, false);
break;
case Utils.CREDENTIAL_PIN:
case Utils.CREDENTIAL_PASSWORD:
- mCredentialView = (AuthCredentialView) factory.inflate(
+ mCredentialView = factory.inflate(
R.layout.auth_credential_password_view, null, false);
break;
default:
@@ -422,16 +473,12 @@
mBackgroundView.setOnClickListener(null);
mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mCredentialView.setContainerView(this);
- mCredentialView.setUserId(mConfig.mUserId);
- mCredentialView.setOperationId(mConfig.mOperationId);
- mCredentialView.setEffectiveUserId(mEffectiveUserId);
- mCredentialView.setCredentialType(credentialType);
- mCredentialView.setCallback(mCredentialCallback);
- mCredentialView.setPromptInfo(mConfig.mPromptInfo);
- mCredentialView.setPanelController(mPanelController, animatePanel);
- mCredentialView.setShouldAnimateContents(animateContents);
- mCredentialView.setBackgroundExecutor(mBackgroundExecutor);
+ mBiometricPromptInteractor.get().useCredentialsForAuthentication(
+ mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId);
+ final CredentialViewModel vm = mCredentialViewModelProvider.get();
+ vm.setAnimateContents(animateContents);
+ ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);
+
mFrameLayout.addView(mCredentialView);
}
@@ -630,11 +677,25 @@
wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle()));
}
+ private void forceExecuteAnimatedIn() {
+ if (mContainerState == STATE_ANIMATING_IN) {
+ //clear all animators
+ if (mCredentialView != null && mCredentialView.isAttachedToWindow()) {
+ mCredentialView.animate().cancel();
+ }
+ mPanelView.animate().cancel();
+ mBiometricView.animate().cancel();
+ animate().cancel();
+ onDialogAnimatedIn();
+ }
+ }
+
@Override
public void dismissWithoutCallback(boolean animate) {
if (animate) {
animateAway(false /* sendReason */, 0 /* reason */);
} else {
+ forceExecuteAnimatedIn();
removeWindowIfAttached();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 8c7e0ef..313ff4157 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -72,6 +72,8 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.CoreStartable;
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -122,6 +124,10 @@
private final Provider<UdfpsController> mUdfpsControllerFactory;
private final Provider<SidefpsController> mSidefpsControllerFactory;
+ // TODO: these should be migrated out once ready
+ @NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
+ @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider;
+
private final Display mDisplay;
private float mScaleFactor = 1f;
// sensor locations without any resolution scaling nor rotation adjustments:
@@ -153,6 +159,7 @@
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+ @NonNull private final SparseBooleanArray mFaceEnrolledForUser;
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mAllFingerprintAuthenticatorsRegistered;
@@ -349,6 +356,15 @@
}
}
}
+ if (mFaceProps == null) {
+ Log.d(TAG, "handleEnrollmentsChanged, mFaceProps is null");
+ } else {
+ for (FaceSensorPropertiesInternal prop : mFaceProps) {
+ if (prop.sensorId == sensorId) {
+ mFaceEnrolledForUser.put(userId, hasEnrollments);
+ }
+ }
+ }
for (Callback cb : mCallbacks) {
cb.onEnrollmentsChanged(modality);
}
@@ -683,6 +699,8 @@
@NonNull LockPatternUtils lockPatternUtils,
@NonNull UdfpsLogger udfpsLogger,
@NonNull StatusBarStateController statusBarStateController,
+ @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+ @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
@Background DelayableExecutor bgExecutor,
@@ -704,8 +722,12 @@
mWindowManager = windowManager;
mInteractionJankMonitor = jankMonitor;
mUdfpsEnrolledForUser = new SparseBooleanArray();
+ mFaceEnrolledForUser = new SparseBooleanArray();
mVibratorHelper = vibrator;
+ mBiometricPromptInteractor = biometricPromptInteractor;
+ mCredentialViewModelProvider = credentialViewModelProvider;
+
mOrientationListener = new BiometricDisplayListener(
context,
mDisplayManager,
@@ -1054,7 +1076,7 @@
return false;
}
- return mFaceManager.hasEnrolledTemplates(userId);
+ return mFaceEnrolledForUser.get(userId);
}
/**
@@ -1068,6 +1090,11 @@
return mUdfpsEnrolledForUser.get(userId);
}
+ /** If BiometricPrompt is currently being shown to the user. */
+ public boolean isShowing() {
+ return mCurrentDialog != null;
+ }
+
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
@@ -1199,7 +1226,8 @@
.setMultiSensorConfig(multiSensorConfig)
.setScaleFactorProvider(() -> getScaleFactor())
.build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle,
- userManager, lockPatternUtils, mInteractionJankMonitor);
+ userManager, lockPatternUtils, mInteractionJankMonitor,
+ mBiometricPromptInteractor, mCredentialViewModelProvider);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
deleted file mode 100644
index 76cd3f4..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.WindowInsets.Type.ime;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.graphics.Insets;
-import android.os.UserHandle;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.View.OnApplyWindowInsetsListener;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.ImeAwareEditText;
-import android.widget.TextView;
-
-import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockscreenCredential;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.systemui.Dumpable;
-import com.android.systemui.R;
-
-import java.io.PrintWriter;
-
-/**
- * Pin and Password UI
- */
-public class AuthCredentialPasswordView extends AuthCredentialView
- implements TextView.OnEditorActionListener, OnApplyWindowInsetsListener, Dumpable {
-
- private static final String TAG = "BiometricPrompt/AuthCredentialPasswordView";
-
- private final InputMethodManager mImm;
- private ImeAwareEditText mPasswordField;
- private ViewGroup mAuthCredentialHeader;
- private ViewGroup mAuthCredentialInput;
- private int mBottomInset = 0;
-
- public AuthCredentialPasswordView(Context context,
- AttributeSet attrs) {
- super(context, attrs);
- mImm = mContext.getSystemService(InputMethodManager.class);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mAuthCredentialHeader = findViewById(R.id.auth_credential_header);
- mAuthCredentialInput = findViewById(R.id.auth_credential_input);
- mPasswordField = findViewById(R.id.lockPassword);
- mPasswordField.setOnEditorActionListener(this);
- // TODO: De-dupe the logic with AuthContainerView
- mPasswordField.setOnKeyListener((v, keyCode, event) -> {
- if (keyCode != KeyEvent.KEYCODE_BACK) {
- return false;
- }
- if (event.getAction() == KeyEvent.ACTION_UP) {
- mContainerView.sendEarlyUserCanceled();
- mContainerView.animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
- }
- return true;
- });
-
- setOnApplyWindowInsetsListener(this);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- mPasswordField.setTextOperationUser(UserHandle.of(mUserId));
- if (mCredentialType == Utils.CREDENTIAL_PIN) {
- mPasswordField.setInputType(
- InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
- }
-
- mPasswordField.requestFocus();
- mPasswordField.scheduleShowSoftInput();
- }
-
- @Override
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- // Check if this was the result of hitting the enter key
- final boolean isSoftImeEvent = event == null
- && (actionId == EditorInfo.IME_NULL
- || actionId == EditorInfo.IME_ACTION_DONE
- || actionId == EditorInfo.IME_ACTION_NEXT);
- final boolean isKeyboardEnterKey = event != null
- && KeyEvent.isConfirmKey(event.getKeyCode())
- && event.getAction() == KeyEvent.ACTION_DOWN;
- if (isSoftImeEvent || isKeyboardEnterKey) {
- checkPasswordAndUnlock();
- return true;
- }
- return false;
- }
-
- private void checkPasswordAndUnlock() {
- try (LockscreenCredential password = mCredentialType == Utils.CREDENTIAL_PIN
- ? LockscreenCredential.createPinOrNone(mPasswordField.getText())
- : LockscreenCredential.createPasswordOrNone(mPasswordField.getText())) {
- if (password.isNone()) {
- return;
- }
-
- // Request LockSettingsService to return the Gatekeeper Password in the
- // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
- // Gatekeeper Password and operationId.
- mPendingLockCheck = LockPatternChecker.verifyCredential(mLockPatternUtils,
- password, mEffectiveUserId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
- this::onCredentialVerified);
- }
- }
-
- @Override
- protected void onCredentialVerified(@NonNull VerifyCredentialResponse response,
- int timeoutMs) {
- super.onCredentialVerified(response, timeoutMs);
-
- if (response.isMatched()) {
- mImm.hideSoftInputFromWindow(getWindowToken(), 0 /* flags */);
- } else {
- mPasswordField.setText("");
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- if (mAuthCredentialInput == null || mAuthCredentialHeader == null || mSubtitleView == null
- || mDescriptionView == null || mPasswordField == null || mErrorView == null) {
- return;
- }
-
- int inputLeftBound;
- int inputTopBound;
- int headerRightBound = right;
- int headerTopBounds = top;
- final int subTitleBottom = (mSubtitleView.getVisibility() == GONE) ? mTitleView.getBottom()
- : mSubtitleView.getBottom();
- final int descBottom = (mDescriptionView.getVisibility() == GONE) ? subTitleBottom
- : mDescriptionView.getBottom();
- if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
- inputTopBound = (bottom - mAuthCredentialInput.getHeight()) / 2;
- inputLeftBound = (right - left) / 2;
- headerRightBound = inputLeftBound;
- headerTopBounds -= Math.min(mIconView.getBottom(), mBottomInset);
- } else {
- inputTopBound =
- descBottom + (bottom - descBottom - mAuthCredentialInput.getHeight()) / 2;
- inputLeftBound = (right - left - mAuthCredentialInput.getWidth()) / 2;
- }
-
- if (mDescriptionView.getBottom() > mBottomInset) {
- mAuthCredentialHeader.layout(left, headerTopBounds, headerRightBound, bottom);
- }
- mAuthCredentialInput.layout(inputLeftBound, inputTopBound, right, bottom);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- final int newWidth = MeasureSpec.getSize(widthMeasureSpec);
- final int newHeight = MeasureSpec.getSize(heightMeasureSpec) - mBottomInset;
-
- setMeasuredDimension(newWidth, newHeight);
-
- final int halfWidthSpec = MeasureSpec.makeMeasureSpec(getWidth() / 2,
- MeasureSpec.AT_MOST);
- final int fullHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.UNSPECIFIED);
- if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
- measureChildren(halfWidthSpec, fullHeightSpec);
- } else {
- measureChildren(widthMeasureSpec, fullHeightSpec);
- }
- }
-
- @NonNull
- @Override
- public WindowInsets onApplyWindowInsets(@NonNull View v, WindowInsets insets) {
-
- final Insets bottomInset = insets.getInsets(ime());
- if (v instanceof AuthCredentialPasswordView && mBottomInset != bottomInset.bottom) {
- mBottomInset = bottomInset.bottom;
- if (mBottomInset > 0
- && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
- mTitleView.setSingleLine(true);
- mTitleView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
- mTitleView.setMarqueeRepeatLimit(-1);
- // select to enable marquee unless a screen reader is enabled
- mTitleView.setSelected(!mAccessibilityManager.isEnabled()
- || !mAccessibilityManager.isTouchExplorationEnabled());
- } else {
- mTitleView.setSingleLine(false);
- mTitleView.setEllipsize(null);
- // select to enable marquee unless a screen reader is enabled
- mTitleView.setSelected(false);
- }
- requestLayout();
- }
- return insets;
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
- pw.println(TAG + "State:");
- pw.println(" mBottomInset=" + mBottomInset);
- pw.println(" mAuthCredentialHeader size=(" + mAuthCredentialHeader.getWidth() + ","
- + mAuthCredentialHeader.getHeight());
- pw.println(" mAuthCredentialInput size=(" + mAuthCredentialInput.getWidth() + ","
- + mAuthCredentialInput.getHeight());
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
deleted file mode 100644
index f9e44a0..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.util.AttributeSet;
-
-import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockPatternView;
-import com.android.internal.widget.LockscreenCredential;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.systemui.R;
-
-import java.util.List;
-
-/**
- * Pattern UI
- */
-public class AuthCredentialPatternView extends AuthCredentialView {
-
- private LockPatternView mLockPatternView;
-
- private class UnlockPatternListener implements LockPatternView.OnPatternListener {
-
- @Override
- public void onPatternStart() {
-
- }
-
- @Override
- public void onPatternCleared() {
-
- }
-
- @Override
- public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
-
- }
-
- @Override
- public void onPatternDetected(List<LockPatternView.Cell> pattern) {
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
- }
-
- mLockPatternView.setEnabled(false);
-
- if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
- // Pattern size is less than the minimum, do not count it as a failed attempt.
- onPatternVerified(VerifyCredentialResponse.ERROR, 0 /* timeoutMs */);
- return;
- }
-
- try (LockscreenCredential credential = LockscreenCredential.createPattern(pattern)) {
- // Request LockSettingsService to return the Gatekeeper Password in the
- // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
- // Gatekeeper Password and operationId.
- mPendingLockCheck = LockPatternChecker.verifyCredential(
- mLockPatternUtils,
- credential,
- mEffectiveUserId,
- LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
- this::onPatternVerified);
- }
- }
-
- private void onPatternVerified(@NonNull VerifyCredentialResponse response, int timeoutMs) {
- AuthCredentialPatternView.this.onCredentialVerified(response, timeoutMs);
- if (timeoutMs > 0) {
- mLockPatternView.setEnabled(false);
- } else {
- mLockPatternView.setEnabled(true);
- }
- }
- }
-
- @Override
- protected void onErrorTimeoutFinish() {
- super.onErrorTimeoutFinish();
- // select to enable marquee unless a screen reader is enabled
- mLockPatternView.setEnabled(!mAccessibilityManager.isEnabled()
- || !mAccessibilityManager.isTouchExplorationEnabled());
- }
-
- public AuthCredentialPatternView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mLockPatternView = findViewById(R.id.lockPattern);
- mLockPatternView.setOnPatternListener(new UnlockPatternListener());
- mLockPatternView.setInStealthMode(
- !mLockPatternUtils.isVisiblePatternEnabled(mUserId));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
deleted file mode 100644
index fa623d1..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ /dev/null
@@ -1,565 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT;
-import static android.app.admin.DevicePolicyResources.UNDEFINED;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AlertDialog;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.biometrics.PromptInfo;
-import android.os.AsyncTask;
-import android.os.CountDownTimer;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.StringRes;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Abstract base class for Pin, Pattern, or Password authentication, for
- * {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)}}
- */
-public abstract class AuthCredentialView extends LinearLayout {
- private static final String TAG = "BiometricPrompt/AuthCredentialView";
- private static final int ERROR_DURATION_MS = 3000;
-
- static final int USER_TYPE_PRIMARY = 1;
- static final int USER_TYPE_MANAGED_PROFILE = 2;
- static final int USER_TYPE_SECONDARY = 3;
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({USER_TYPE_PRIMARY, USER_TYPE_MANAGED_PROFILE, USER_TYPE_SECONDARY})
- private @interface UserType {}
-
- protected final Handler mHandler;
- protected final LockPatternUtils mLockPatternUtils;
-
- protected final AccessibilityManager mAccessibilityManager;
- private final UserManager mUserManager;
- private final DevicePolicyManager mDevicePolicyManager;
-
- private PromptInfo mPromptInfo;
- private AuthPanelController mPanelController;
- private boolean mShouldAnimatePanel;
- private boolean mShouldAnimateContents;
-
- protected TextView mTitleView;
- protected TextView mSubtitleView;
- protected TextView mDescriptionView;
- protected ImageView mIconView;
- protected TextView mErrorView;
-
- protected @Utils.CredentialType int mCredentialType;
- protected AuthContainerView mContainerView;
- protected Callback mCallback;
- protected AsyncTask<?, ?, ?> mPendingLockCheck;
- protected int mUserId;
- protected long mOperationId;
- protected int mEffectiveUserId;
- protected ErrorTimer mErrorTimer;
-
- protected @Background DelayableExecutor mBackgroundExecutor;
-
- interface Callback {
- void onCredentialMatched(byte[] attestation);
- }
-
- protected static class ErrorTimer extends CountDownTimer {
- private final TextView mErrorView;
- private final Context mContext;
-
- /**
- * @param millisInFuture The number of millis in the future from the call
- * to {@link #start()} until the countdown is done and {@link
- * #onFinish()}
- * is called.
- * @param countDownInterval The interval along the way to receive
- * {@link #onTick(long)} callbacks.
- */
- public ErrorTimer(Context context, long millisInFuture, long countDownInterval,
- TextView errorView) {
- super(millisInFuture, countDownInterval);
- mErrorView = errorView;
- mContext = context;
- }
-
- @Override
- public void onTick(long millisUntilFinished) {
- final int secondsCountdown = (int) (millisUntilFinished / 1000);
- mErrorView.setText(mContext.getString(
- R.string.biometric_dialog_credential_too_many_attempts, secondsCountdown));
- }
-
- @Override
- public void onFinish() {
- if (mErrorView != null) {
- mErrorView.setText("");
- }
- }
- }
-
- protected final Runnable mClearErrorRunnable = new Runnable() {
- @Override
- public void run() {
- if (mErrorView != null) {
- mErrorView.setText("");
- }
- }
- };
-
- public AuthCredentialView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- mLockPatternUtils = new LockPatternUtils(mContext);
- mHandler = new Handler(Looper.getMainLooper());
- mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
- mUserManager = mContext.getSystemService(UserManager.class);
- mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
- }
-
- protected void showError(String error) {
- if (mHandler != null) {
- mHandler.removeCallbacks(mClearErrorRunnable);
- mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS);
- }
- if (mErrorView != null) {
- mErrorView.setText(error);
- }
- }
-
- private void setTextOrHide(TextView view, CharSequence text) {
- if (TextUtils.isEmpty(text)) {
- view.setVisibility(View.GONE);
- } else {
- view.setText(text);
- }
-
- Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
- }
-
- private void setText(TextView view, CharSequence text) {
- view.setText(text);
- }
-
- void setUserId(int userId) {
- mUserId = userId;
- }
-
- void setOperationId(long operationId) {
- mOperationId = operationId;
- }
-
- void setEffectiveUserId(int effectiveUserId) {
- mEffectiveUserId = effectiveUserId;
- }
-
- void setCredentialType(@Utils.CredentialType int credentialType) {
- mCredentialType = credentialType;
- }
-
- void setCallback(Callback callback) {
- mCallback = callback;
- }
-
- void setPromptInfo(PromptInfo promptInfo) {
- mPromptInfo = promptInfo;
- }
-
- void setPanelController(AuthPanelController panelController, boolean animatePanel) {
- mPanelController = panelController;
- mShouldAnimatePanel = animatePanel;
- }
-
- void setShouldAnimateContents(boolean animateContents) {
- mShouldAnimateContents = animateContents;
- }
-
- void setContainerView(AuthContainerView containerView) {
- mContainerView = containerView;
- }
-
- void setBackgroundExecutor(@Background DelayableExecutor bgExecutor) {
- mBackgroundExecutor = bgExecutor;
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- final CharSequence title = getTitle(mPromptInfo);
- setText(mTitleView, title);
- setTextOrHide(mSubtitleView, getSubtitle(mPromptInfo));
- setTextOrHide(mDescriptionView, getDescription(mPromptInfo));
- announceForAccessibility(title);
-
- if (mIconView != null) {
- final boolean isManagedProfile = Utils.isManagedProfile(mContext, mEffectiveUserId);
- final Drawable image;
- if (isManagedProfile) {
- image = getResources().getDrawable(R.drawable.auth_dialog_enterprise,
- mContext.getTheme());
- } else {
- image = getResources().getDrawable(R.drawable.auth_dialog_lock,
- mContext.getTheme());
- }
- mIconView.setImageDrawable(image);
- }
-
- // Only animate this if we're transitioning from a biometric view.
- if (mShouldAnimateContents) {
- setTranslationY(getResources()
- .getDimension(R.dimen.biometric_dialog_credential_translation_offset));
- setAlpha(0);
-
- postOnAnimation(() -> {
- animate().translationY(0)
- .setDuration(AuthDialog.ANIMATE_CREDENTIAL_INITIAL_DURATION_MS)
- .alpha(1.f)
- .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
- .withLayer()
- .start();
- });
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mErrorTimer != null) {
- mErrorTimer.cancel();
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mTitleView = findViewById(R.id.title);
- mSubtitleView = findViewById(R.id.subtitle);
- mDescriptionView = findViewById(R.id.description);
- mIconView = findViewById(R.id.icon);
- mErrorView = findViewById(R.id.error);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- if (mShouldAnimatePanel) {
- // Credential view is always full screen.
- mPanelController.setUseFullScreen(true);
- mPanelController.updateForContentDimensions(mPanelController.getContainerWidth(),
- mPanelController.getContainerHeight(), 0 /* animateDurationMs */);
- mShouldAnimatePanel = false;
- }
- }
-
- protected void onErrorTimeoutFinish() {}
-
- protected void onCredentialVerified(@NonNull VerifyCredentialResponse response, int timeoutMs) {
- if (response.isMatched()) {
- mClearErrorRunnable.run();
- mLockPatternUtils.userPresent(mEffectiveUserId);
-
- // The response passed into this method contains the Gatekeeper Password. We still
- // have to request Gatekeeper to create a Hardware Auth Token with the
- // Gatekeeper Password and Challenge (keystore operationId in this case)
- final long pwHandle = response.getGatekeeperPasswordHandle();
- final VerifyCredentialResponse gkResponse = mLockPatternUtils
- .verifyGatekeeperPasswordHandle(pwHandle, mOperationId, mEffectiveUserId);
-
- mCallback.onCredentialMatched(gkResponse.getGatekeeperHAT());
- mLockPatternUtils.removeGatekeeperPasswordHandle(pwHandle);
- } else {
- if (timeoutMs > 0) {
- mHandler.removeCallbacks(mClearErrorRunnable);
- long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
- mEffectiveUserId, timeoutMs);
- mErrorTimer = new ErrorTimer(mContext,
- deadline - SystemClock.elapsedRealtime(),
- LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS,
- mErrorView) {
- @Override
- public void onFinish() {
- onErrorTimeoutFinish();
- mClearErrorRunnable.run();
- }
- };
- mErrorTimer.start();
- } else {
- final boolean didUpdateErrorText = reportFailedAttempt();
- if (!didUpdateErrorText) {
- final @StringRes int errorRes;
- switch (mCredentialType) {
- case Utils.CREDENTIAL_PIN:
- errorRes = R.string.biometric_dialog_wrong_pin;
- break;
- case Utils.CREDENTIAL_PATTERN:
- errorRes = R.string.biometric_dialog_wrong_pattern;
- break;
- case Utils.CREDENTIAL_PASSWORD:
- default:
- errorRes = R.string.biometric_dialog_wrong_password;
- break;
- }
- showError(getResources().getString(errorRes));
- }
- }
- }
- }
-
- private boolean reportFailedAttempt() {
- boolean result = updateErrorMessage(
- mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
- mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
- return result;
- }
-
- private boolean updateErrorMessage(int numAttempts) {
- // Don't show any message if there's no maximum number of attempts.
- final int maxAttempts = mLockPatternUtils.getMaximumFailedPasswordsForWipe(
- mEffectiveUserId);
- if (maxAttempts <= 0 || numAttempts <= 0) {
- return false;
- }
-
- // Update the on-screen error string.
- if (mErrorView != null) {
- final String message = getResources().getString(
- R.string.biometric_dialog_credential_attempts_before_wipe,
- numAttempts,
- maxAttempts);
- showError(message);
- }
-
- // Only show dialog if <=1 attempts are left before wiping.
- final int remainingAttempts = maxAttempts - numAttempts;
- if (remainingAttempts == 1) {
- showLastAttemptBeforeWipeDialog();
- } else if (remainingAttempts <= 0) {
- showNowWipingDialog();
- }
- return true;
- }
-
- private void showLastAttemptBeforeWipeDialog() {
- mBackgroundExecutor.execute(() -> {
- final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
- .setTitle(R.string.biometric_dialog_last_attempt_before_wipe_dialog_title)
- .setMessage(
- getLastAttemptBeforeWipeMessage(getUserTypeForWipe(), mCredentialType))
- .setPositiveButton(android.R.string.ok, null)
- .create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
- mHandler.post(alertDialog::show);
- });
- }
-
- private void showNowWipingDialog() {
- mBackgroundExecutor.execute(() -> {
- String nowWipingMessage = getNowWipingMessage(getUserTypeForWipe());
- final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
- .setMessage(nowWipingMessage)
- .setPositiveButton(
- com.android.settingslib.R.string.failed_attempts_now_wiping_dialog_dismiss,
- null /* OnClickListener */)
- .setOnDismissListener(
- dialog -> mContainerView.animateAway(
- AuthDialogCallback.DISMISSED_ERROR))
- .create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
- mHandler.post(alertDialog::show);
- });
- }
-
- private @UserType int getUserTypeForWipe() {
- final UserInfo userToBeWiped = mUserManager.getUserInfo(
- mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId));
- if (userToBeWiped == null || userToBeWiped.isPrimary()) {
- return USER_TYPE_PRIMARY;
- } else if (userToBeWiped.isManagedProfile()) {
- return USER_TYPE_MANAGED_PROFILE;
- } else {
- return USER_TYPE_SECONDARY;
- }
- }
-
- // This should not be called on the main thread to avoid making an IPC.
- private String getLastAttemptBeforeWipeMessage(
- @UserType int userType, @Utils.CredentialType int credentialType) {
- switch (userType) {
- case USER_TYPE_PRIMARY:
- return getLastAttemptBeforeWipeDeviceMessage(credentialType);
- case USER_TYPE_MANAGED_PROFILE:
- return getLastAttemptBeforeWipeProfileMessage(credentialType);
- case USER_TYPE_SECONDARY:
- return getLastAttemptBeforeWipeUserMessage(credentialType);
- default:
- throw new IllegalArgumentException("Unrecognized user type:" + userType);
- }
- }
-
- private String getLastAttemptBeforeWipeDeviceMessage(
- @Utils.CredentialType int credentialType) {
- switch (credentialType) {
- case Utils.CREDENTIAL_PIN:
- return mContext.getString(
- R.string.biometric_dialog_last_pin_attempt_before_wipe_device);
- case Utils.CREDENTIAL_PATTERN:
- return mContext.getString(
- R.string.biometric_dialog_last_pattern_attempt_before_wipe_device);
- case Utils.CREDENTIAL_PASSWORD:
- default:
- return mContext.getString(
- R.string.biometric_dialog_last_password_attempt_before_wipe_device);
- }
- }
-
- // This should not be called on the main thread to avoid making an IPC.
- private String getLastAttemptBeforeWipeProfileMessage(
- @Utils.CredentialType int credentialType) {
- return mDevicePolicyManager.getResources().getString(
- getLastAttemptBeforeWipeProfileUpdatableStringId(credentialType),
- () -> getLastAttemptBeforeWipeProfileDefaultMessage(credentialType));
- }
-
- private static String getLastAttemptBeforeWipeProfileUpdatableStringId(
- @Utils.CredentialType int credentialType) {
- switch (credentialType) {
- case Utils.CREDENTIAL_PIN:
- return BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT;
- case Utils.CREDENTIAL_PATTERN:
- return BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT;
- case Utils.CREDENTIAL_PASSWORD:
- default:
- return BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT;
- }
- }
-
- private String getLastAttemptBeforeWipeProfileDefaultMessage(
- @Utils.CredentialType int credentialType) {
- int resId;
- switch (credentialType) {
- case Utils.CREDENTIAL_PIN:
- resId = R.string.biometric_dialog_last_pin_attempt_before_wipe_profile;
- break;
- case Utils.CREDENTIAL_PATTERN:
- resId = R.string.biometric_dialog_last_pattern_attempt_before_wipe_profile;
- break;
- case Utils.CREDENTIAL_PASSWORD:
- default:
- resId = R.string.biometric_dialog_last_password_attempt_before_wipe_profile;
- }
- return mContext.getString(resId);
- }
-
- private String getLastAttemptBeforeWipeUserMessage(
- @Utils.CredentialType int credentialType) {
- int resId;
- switch (credentialType) {
- case Utils.CREDENTIAL_PIN:
- resId = R.string.biometric_dialog_last_pin_attempt_before_wipe_user;
- break;
- case Utils.CREDENTIAL_PATTERN:
- resId = R.string.biometric_dialog_last_pattern_attempt_before_wipe_user;
- break;
- case Utils.CREDENTIAL_PASSWORD:
- default:
- resId = R.string.biometric_dialog_last_password_attempt_before_wipe_user;
- }
- return mContext.getString(resId);
- }
-
- private String getNowWipingMessage(@UserType int userType) {
- return mDevicePolicyManager.getResources().getString(
- getNowWipingUpdatableStringId(userType),
- () -> getNowWipingDefaultMessage(userType));
- }
-
- private String getNowWipingUpdatableStringId(@UserType int userType) {
- switch (userType) {
- case USER_TYPE_MANAGED_PROFILE:
- return BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS;
- default:
- return UNDEFINED;
- }
- }
-
- private String getNowWipingDefaultMessage(@UserType int userType) {
- int resId;
- switch (userType) {
- case USER_TYPE_PRIMARY:
- resId = com.android.settingslib.R.string.failed_attempts_now_wiping_device;
- break;
- case USER_TYPE_MANAGED_PROFILE:
- resId = com.android.settingslib.R.string.failed_attempts_now_wiping_profile;
- break;
- case USER_TYPE_SECONDARY:
- resId = com.android.settingslib.R.string.failed_attempts_now_wiping_user;
- break;
- default:
- throw new IllegalArgumentException("Unrecognized user type:" + userType);
- }
- return mContext.getString(resId);
- }
-
- @Nullable
- private static CharSequence getTitle(@NonNull PromptInfo promptInfo) {
- final CharSequence credentialTitle = promptInfo.getDeviceCredentialTitle();
- return credentialTitle != null ? credentialTitle : promptInfo.getTitle();
- }
-
- @Nullable
- private static CharSequence getSubtitle(@NonNull PromptInfo promptInfo) {
- final CharSequence credentialSubtitle = promptInfo.getDeviceCredentialSubtitle();
- return credentialSubtitle != null ? credentialSubtitle : promptInfo.getSubtitle();
- }
-
- @Nullable
- private static CharSequence getDescription(@NonNull PromptInfo promptInfo) {
- final CharSequence credentialDescription = promptInfo.getDeviceCredentialDescription();
- return credentialDescription != null ? credentialDescription : promptInfo.getDescription();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
index f1e42e0..5c616f0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -177,11 +177,11 @@
}
}
- int getContainerWidth() {
+ public int getContainerWidth() {
return mContainerWidth;
}
- int getContainerHeight() {
+ public int getContainerHeight() {
return mContainerHeight;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index b5d81f2..7c0c3b7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -16,32 +16,45 @@
package com.android.systemui.biometrics.dagger
+import com.android.systemui.biometrics.data.repository.PromptRepository
+import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
+import com.android.systemui.biometrics.domain.interactor.CredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.concurrency.ThreadFactory
+import dagger.Binds
import dagger.Module
import dagger.Provides
import java.util.concurrent.Executor
import javax.inject.Qualifier
-/**
- * Dagger module for all things biometric.
- */
+/** Dagger module for all things biometric. */
@Module
-object BiometricsModule {
+interface BiometricsModule {
- /** Background [Executor] for HAL related operations. */
- @Provides
+ @Binds
@SysUISingleton
- @JvmStatic
- @BiometricsBackground
- fun providesPluginExecutor(threadFactory: ThreadFactory): Executor =
- threadFactory.buildExecutorOnNewThread("biometrics")
+ fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository
+
+ @Binds
+ @SysUISingleton
+ fun providesCredentialInteractor(impl: CredentialInteractorImpl): CredentialInteractor
+
+ companion object {
+ /** Background [Executor] for HAL related operations. */
+ @Provides
+ @SysUISingleton
+ @JvmStatic
+ @BiometricsBackground
+ fun providesPluginExecutor(threadFactory: ThreadFactory): Executor =
+ threadFactory.buildExecutorOnNewThread("biometrics")
+ }
}
/**
- * Background executor for HAL operations that are latency sensitive but too
- * slow to run on the main thread. Prefer the shared executors, such as
- * [com.android.systemui.dagger.qualifiers.Background] when a HAL is not directly involved.
+ * Background executor for HAL operations that are latency sensitive but too slow to run on the main
+ * thread. Prefer the shared executors, such as [com.android.systemui.dagger.qualifiers.Background]
+ * when a HAL is not directly involved.
*/
@Qualifier
@MustBeDocumented
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
copy to packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt
index 581dafa3..e82646f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt
@@ -14,10 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package com.android.systemui.biometrics.data.model
-/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
-enum class KeyguardQuickAffordancePosition {
- BOTTOM_START,
- BOTTOM_END,
+import com.android.systemui.biometrics.Utils
+
+// TODO(b/251476085): this should eventually replace Utils.CredentialType
+/** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */
+enum class PromptKind {
+ ANY_BIOMETRIC,
+ PIN,
+ PATTERN,
+ PASSWORD,
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
new file mode 100644
index 0000000..92a13cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -0,0 +1,102 @@
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.biometrics.PromptInfo
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A repository for the global state of BiometricPrompt.
+ *
+ * There is never more than one instance of the prompt at any given time.
+ */
+interface PromptRepository {
+
+ /** If the prompt is showing. */
+ val isShowing: Flow<Boolean>
+
+ /** The app-specific details to show in the prompt. */
+ val promptInfo: StateFlow<PromptInfo?>
+
+ /** The user that the prompt is for. */
+ val userId: StateFlow<Int?>
+
+ /** The gatekeeper challenge, if one is associated with this prompt. */
+ val challenge: StateFlow<Long?>
+
+ /** The kind of credential to use (biometric, pin, pattern, etc.). */
+ val kind: StateFlow<PromptKind>
+
+ /** Update the prompt configuration, which should be set before [isShowing]. */
+ fun setPrompt(
+ promptInfo: PromptInfo,
+ userId: Int,
+ gatekeeperChallenge: Long?,
+ kind: PromptKind = PromptKind.ANY_BIOMETRIC,
+ )
+
+ /** Unset the prompt info. */
+ fun unsetPrompt()
+}
+
+@SysUISingleton
+class PromptRepositoryImpl @Inject constructor(private val authController: AuthController) :
+ PromptRepository {
+
+ override val isShowing: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : AuthController.Callback {
+ override fun onBiometricPromptShown() =
+ trySendWithFailureLogging(true, TAG, "set isShowing")
+
+ override fun onBiometricPromptDismissed() =
+ trySendWithFailureLogging(false, TAG, "unset isShowing")
+ }
+ authController.addCallback(callback)
+ trySendWithFailureLogging(authController.isShowing, TAG, "update isShowing")
+ awaitClose { authController.removeCallback(callback) }
+ }
+
+ private val _promptInfo: MutableStateFlow<PromptInfo?> = MutableStateFlow(null)
+ override val promptInfo = _promptInfo.asStateFlow()
+
+ private val _challenge: MutableStateFlow<Long?> = MutableStateFlow(null)
+ override val challenge: StateFlow<Long?> = _challenge.asStateFlow()
+
+ private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null)
+ override val userId = _userId.asStateFlow()
+
+ private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.ANY_BIOMETRIC)
+ override val kind = _kind.asStateFlow()
+
+ override fun setPrompt(
+ promptInfo: PromptInfo,
+ userId: Int,
+ gatekeeperChallenge: Long?,
+ kind: PromptKind,
+ ) {
+ _kind.value = kind
+ _userId.value = userId
+ _challenge.value = gatekeeperChallenge
+ _promptInfo.value = promptInfo
+ }
+
+ override fun unsetPrompt() {
+ _promptInfo.value = null
+ _userId.value = null
+ _challenge.value = null
+ _kind.value = PromptKind.ANY_BIOMETRIC
+ }
+
+ companion object {
+ private const val TAG = "BiometricPromptRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
new file mode 100644
index 0000000..1f1a1b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -0,0 +1,282 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources
+import android.content.Context
+import android.os.UserManager
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
+import com.android.internal.widget.VerifyCredentialResponse
+import com.android.systemui.R
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * A wrapper for [LockPatternUtils] to verify PIN, pattern, or password credentials.
+ *
+ * This class also uses the [DevicePolicyManager] to generate appropriate error messages when policy
+ * exceptions are raised (i.e. wipe device due to excessive failed attempts, etc.).
+ */
+interface CredentialInteractor {
+ /** If the user's pattern credential should be hidden */
+ fun isStealthModeActive(userId: Int): Boolean
+
+ /** Get the effective user id (profile owner, if one exists) */
+ fun getCredentialOwnerOrSelfId(userId: Int): Int
+
+ /**
+ * Verifies a credential and returns a stream of results.
+ *
+ * The final emitted value will either be a [CredentialStatus.Fail.Error] or a
+ * [CredentialStatus.Success.Verified].
+ */
+ fun verifyCredential(
+ request: BiometricPromptRequest.Credential,
+ credential: LockscreenCredential,
+ ): Flow<CredentialStatus>
+}
+
+/** Standard implementation of [CredentialInteractor]. */
+class CredentialInteractorImpl
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val lockPatternUtils: LockPatternUtils,
+ private val userManager: UserManager,
+ private val devicePolicyManager: DevicePolicyManager,
+ private val systemClock: SystemClock,
+) : CredentialInteractor {
+
+ override fun isStealthModeActive(userId: Int): Boolean =
+ !lockPatternUtils.isVisiblePatternEnabled(userId)
+
+ override fun getCredentialOwnerOrSelfId(userId: Int): Int =
+ userManager.getCredentialOwnerProfile(userId)
+
+ override fun verifyCredential(
+ request: BiometricPromptRequest.Credential,
+ credential: LockscreenCredential,
+ ): Flow<CredentialStatus> = flow {
+ // Request LockSettingsService to return the Gatekeeper Password in the
+ // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
+ // Gatekeeper Password and operationId.
+ val effectiveUserId = request.userInfo.deviceCredentialOwnerId
+ val response =
+ lockPatternUtils.verifyCredential(
+ credential,
+ effectiveUserId,
+ LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE
+ )
+
+ if (response.isMatched) {
+ lockPatternUtils.userPresent(effectiveUserId)
+
+ // The response passed into this method contains the Gatekeeper
+ // Password. We still have to request Gatekeeper to create a
+ // Hardware Auth Token with the Gatekeeper Password and Challenge
+ // (keystore operationId in this case)
+ val pwHandle = response.gatekeeperPasswordHandle
+ val gkResponse: VerifyCredentialResponse =
+ lockPatternUtils.verifyGatekeeperPasswordHandle(
+ pwHandle,
+ request.operationInfo.gatekeeperChallenge,
+ effectiveUserId
+ )
+ val hat = gkResponse.gatekeeperHAT
+ lockPatternUtils.removeGatekeeperPasswordHandle(pwHandle)
+ emit(CredentialStatus.Success.Verified(hat))
+ } else if (response.timeout > 0) {
+ // if requests are being throttled, update the error message every
+ // second until the temporary lock has expired
+ val deadline: Long =
+ lockPatternUtils.setLockoutAttemptDeadline(effectiveUserId, response.timeout)
+ val interval = LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS
+ var remaining = deadline - systemClock.elapsedRealtime()
+ while (remaining > 0) {
+ emit(
+ CredentialStatus.Fail.Throttled(
+ applicationContext.getString(
+ R.string.biometric_dialog_credential_too_many_attempts,
+ remaining / 1000
+ )
+ )
+ )
+ delay(interval)
+ remaining -= interval
+ }
+ emit(CredentialStatus.Fail.Error(""))
+ } else { // bad request, but not throttled
+ val numAttempts = lockPatternUtils.getCurrentFailedPasswordAttempts(effectiveUserId) + 1
+ val maxAttempts = lockPatternUtils.getMaximumFailedPasswordsForWipe(effectiveUserId)
+ if (maxAttempts <= 0 || numAttempts <= 0) {
+ // use a generic message if there's no maximum number of attempts
+ emit(CredentialStatus.Fail.Error())
+ } else {
+ val remainingAttempts = (maxAttempts - numAttempts).coerceAtLeast(0)
+ emit(
+ CredentialStatus.Fail.Error(
+ applicationContext.getString(
+ R.string.biometric_dialog_credential_attempts_before_wipe,
+ numAttempts,
+ maxAttempts
+ ),
+ remainingAttempts,
+ fetchFinalAttemptMessageOrNull(request, remainingAttempts)
+ )
+ )
+ }
+ lockPatternUtils.reportFailedPasswordAttempt(effectiveUserId)
+ }
+ }
+
+ private fun fetchFinalAttemptMessageOrNull(
+ request: BiometricPromptRequest.Credential,
+ remainingAttempts: Int?,
+ ): String? =
+ if (remainingAttempts != null && remainingAttempts <= 1) {
+ applicationContext.getFinalAttemptMessageOrBlank(
+ request,
+ devicePolicyManager,
+ userManager.getUserTypeForWipe(
+ devicePolicyManager,
+ request.userInfo.deviceCredentialOwnerId
+ ),
+ remainingAttempts
+ )
+ } else {
+ null
+ }
+}
+
+private enum class UserType {
+ PRIMARY,
+ MANAGED_PROFILE,
+ SECONDARY,
+}
+
+private fun UserManager.getUserTypeForWipe(
+ devicePolicyManager: DevicePolicyManager,
+ effectiveUserId: Int,
+): UserType {
+ val userToBeWiped =
+ getUserInfo(
+ devicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(effectiveUserId)
+ )
+ return when {
+ userToBeWiped == null || userToBeWiped.isPrimary -> UserType.PRIMARY
+ userToBeWiped.isManagedProfile -> UserType.MANAGED_PROFILE
+ else -> UserType.SECONDARY
+ }
+}
+
+private fun Context.getFinalAttemptMessageOrBlank(
+ request: BiometricPromptRequest.Credential,
+ devicePolicyManager: DevicePolicyManager,
+ userType: UserType,
+ remaining: Int,
+): String =
+ when {
+ remaining == 1 -> getLastAttemptBeforeWipeMessage(request, devicePolicyManager, userType)
+ remaining <= 0 -> getNowWipingMessage(devicePolicyManager, userType)
+ else -> ""
+ }
+
+private fun Context.getLastAttemptBeforeWipeMessage(
+ request: BiometricPromptRequest.Credential,
+ devicePolicyManager: DevicePolicyManager,
+ userType: UserType,
+): String =
+ when (userType) {
+ UserType.PRIMARY -> getLastAttemptBeforeWipeDeviceMessage(request)
+ UserType.MANAGED_PROFILE ->
+ getLastAttemptBeforeWipeProfileMessage(request, devicePolicyManager)
+ UserType.SECONDARY -> getLastAttemptBeforeWipeUserMessage(request)
+ }
+
+private fun Context.getLastAttemptBeforeWipeDeviceMessage(
+ request: BiometricPromptRequest.Credential,
+): String {
+ val id =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ R.string.biometric_dialog_last_pin_attempt_before_wipe_device
+ is BiometricPromptRequest.Credential.Pattern ->
+ R.string.biometric_dialog_last_pattern_attempt_before_wipe_device
+ is BiometricPromptRequest.Credential.Password ->
+ R.string.biometric_dialog_last_password_attempt_before_wipe_device
+ }
+ return getString(id)
+}
+
+private fun Context.getLastAttemptBeforeWipeProfileMessage(
+ request: BiometricPromptRequest.Credential,
+ devicePolicyManager: DevicePolicyManager,
+): String {
+ val id =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT
+ is BiometricPromptRequest.Credential.Pattern ->
+ DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT
+ is BiometricPromptRequest.Credential.Password ->
+ DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT
+ }
+ return devicePolicyManager.resources.getString(id) {
+ // use fallback a string if not found
+ val defaultId =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ R.string.biometric_dialog_last_pin_attempt_before_wipe_profile
+ is BiometricPromptRequest.Credential.Pattern ->
+ R.string.biometric_dialog_last_pattern_attempt_before_wipe_profile
+ is BiometricPromptRequest.Credential.Password ->
+ R.string.biometric_dialog_last_password_attempt_before_wipe_profile
+ }
+ getString(defaultId)
+ }
+}
+
+private fun Context.getLastAttemptBeforeWipeUserMessage(
+ request: BiometricPromptRequest.Credential,
+): String {
+ val resId =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ R.string.biometric_dialog_last_pin_attempt_before_wipe_user
+ is BiometricPromptRequest.Credential.Pattern ->
+ R.string.biometric_dialog_last_pattern_attempt_before_wipe_user
+ is BiometricPromptRequest.Credential.Password ->
+ R.string.biometric_dialog_last_password_attempt_before_wipe_user
+ }
+ return getString(resId)
+}
+
+private fun Context.getNowWipingMessage(
+ devicePolicyManager: DevicePolicyManager,
+ userType: UserType,
+): String {
+ val id =
+ when (userType) {
+ UserType.MANAGED_PROFILE ->
+ DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS
+ else -> DevicePolicyResources.UNDEFINED
+ }
+ return devicePolicyManager.resources.getString(id) {
+ // use fallback a string if not found
+ val defaultId =
+ when (userType) {
+ UserType.PRIMARY ->
+ com.android.settingslib.R.string.failed_attempts_now_wiping_device
+ UserType.MANAGED_PROFILE ->
+ com.android.settingslib.R.string.failed_attempts_now_wiping_profile
+ UserType.SECONDARY ->
+ com.android.settingslib.R.string.failed_attempts_now_wiping_user
+ }
+ getString(defaultId)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialStatus.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialStatus.kt
new file mode 100644
index 0000000..40b7612
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialStatus.kt
@@ -0,0 +1,23 @@
+package com.android.systemui.biometrics.domain.interactor
+
+/** Result of a [CredentialInteractor.verifyCredential] check. */
+sealed interface CredentialStatus {
+ /** A successful result. */
+ sealed interface Success : CredentialStatus {
+ /** The credential is valid and a [hat] has been generated. */
+ data class Verified(val hat: ByteArray) : Success
+ }
+ /** A failed result. */
+ sealed interface Fail : CredentialStatus {
+ val error: String?
+
+ /** The credential check failed with an [error]. */
+ data class Error(
+ override val error: String? = null,
+ val remainingAttempts: Int? = null,
+ val urgentMessage: String? = null,
+ ) : Fail
+ /** The credential check failed with an [error] and is temporarily locked out. */
+ data class Throttled(override val error: String) : Fail
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
new file mode 100644
index 0000000..6362c2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -0,0 +1,189 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import android.hardware.biometrics.PromptInfo
+import com.android.internal.widget.LockPatternView
+import com.android.internal.widget.LockscreenCredential
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.biometrics.data.repository.PromptRepository
+import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.lastOrNull
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.withContext
+
+/**
+ * Business logic for BiometricPrompt's CredentialViews, which primarily includes checking a users
+ * PIN, pattern, or password credential instead of a biometric.
+ */
+class BiometricPromptCredentialInteractor
+@Inject
+constructor(
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val biometricPromptRepository: PromptRepository,
+ private val credentialInteractor: CredentialInteractor,
+) {
+ /** If the prompt is currently showing. */
+ val isShowing: Flow<Boolean> = biometricPromptRepository.isShowing
+
+ /** Metadata about the current credential prompt, including app-supplied preferences. */
+ val prompt: Flow<BiometricPromptRequest?> =
+ combine(
+ biometricPromptRepository.promptInfo,
+ biometricPromptRepository.challenge,
+ biometricPromptRepository.userId,
+ biometricPromptRepository.kind
+ ) { promptInfo, challenge, userId, kind ->
+ if (promptInfo == null || userId == null || challenge == null) {
+ return@combine null
+ }
+
+ when (kind) {
+ PromptKind.PIN ->
+ BiometricPromptRequest.Credential.Pin(
+ info = promptInfo,
+ userInfo = userInfo(userId),
+ operationInfo = operationInfo(challenge)
+ )
+ PromptKind.PATTERN ->
+ BiometricPromptRequest.Credential.Pattern(
+ info = promptInfo,
+ userInfo = userInfo(userId),
+ operationInfo = operationInfo(challenge),
+ stealthMode = credentialInteractor.isStealthModeActive(userId)
+ )
+ PromptKind.PASSWORD ->
+ BiometricPromptRequest.Credential.Password(
+ info = promptInfo,
+ userInfo = userInfo(userId),
+ operationInfo = operationInfo(challenge)
+ )
+ else -> null
+ }
+ }
+ .distinctUntilChanged()
+
+ private fun userInfo(userId: Int): BiometricUserInfo =
+ BiometricUserInfo(
+ userId = userId,
+ deviceCredentialOwnerId = credentialInteractor.getCredentialOwnerOrSelfId(userId)
+ )
+
+ private fun operationInfo(challenge: Long): BiometricOperationInfo =
+ BiometricOperationInfo(gatekeeperChallenge = challenge)
+
+ /** Most recent error due to [verifyCredential]. */
+ private val _verificationError = MutableStateFlow<CredentialStatus.Fail?>(null)
+ val verificationError: Flow<CredentialStatus.Fail?> = _verificationError.asStateFlow()
+
+ /** Update the current request to use credential-based authentication instead of biometrics. */
+ fun useCredentialsForAuthentication(
+ promptInfo: PromptInfo,
+ @Utils.CredentialType kind: Int,
+ userId: Int,
+ challenge: Long,
+ ) {
+ biometricPromptRepository.setPrompt(
+ promptInfo,
+ userId,
+ challenge,
+ kind.asBiometricPromptCredential()
+ )
+ }
+
+ /** Unset the current authentication request. */
+ fun resetPrompt() {
+ biometricPromptRepository.unsetPrompt()
+ }
+
+ /**
+ * Check a credential and return the attestation token (HAT) if successful.
+ *
+ * This method will not return if credential checks are being throttled until the throttling has
+ * expired and the user can try again. It will periodically update the [verificationError] until
+ * cancelled or the throttling has completed. If the request is not throttled, but unsuccessful,
+ * the [verificationError] will be set and an optional
+ * [CredentialStatus.Fail.Error.urgentMessage] message may be provided to indicate additional
+ * hints to the user (i.e. device will be wiped on next failure, etc.).
+ *
+ * The check happens on the background dispatcher given in the constructor.
+ */
+ suspend fun checkCredential(
+ request: BiometricPromptRequest.Credential,
+ text: CharSequence? = null,
+ pattern: List<LockPatternView.Cell>? = null,
+ ): CredentialStatus =
+ withContext(bgDispatcher) {
+ val credential =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ LockscreenCredential.createPinOrNone(text ?: "")
+ is BiometricPromptRequest.Credential.Password ->
+ LockscreenCredential.createPasswordOrNone(text ?: "")
+ is BiometricPromptRequest.Credential.Pattern ->
+ LockscreenCredential.createPattern(pattern ?: listOf())
+ }
+
+ credential.use { c -> verifyCredential(request, c) }
+ }
+
+ private suspend fun verifyCredential(
+ request: BiometricPromptRequest.Credential,
+ credential: LockscreenCredential?
+ ): CredentialStatus {
+ if (credential == null || credential.isNone) {
+ return CredentialStatus.Fail.Error()
+ }
+
+ val finalStatus =
+ credentialInteractor
+ .verifyCredential(request, credential)
+ .onEach { status ->
+ when (status) {
+ is CredentialStatus.Success -> _verificationError.value = null
+ is CredentialStatus.Fail -> _verificationError.value = status
+ }
+ }
+ .lastOrNull()
+
+ return finalStatus ?: CredentialStatus.Fail.Error()
+ }
+
+ /**
+ * Report a user-visible error.
+ *
+ * Use this instead of calling [verifyCredential] when it is not necessary because the check
+ * will obviously fail (i.e. too short, empty, etc.)
+ */
+ fun setVerificationError(error: CredentialStatus.Fail.Error?) {
+ if (error != null) {
+ _verificationError.value = error
+ } else {
+ resetVerificationError()
+ }
+ }
+
+ /** Clear the current error message, if any. */
+ fun resetVerificationError() {
+ _verificationError.value = null
+ }
+}
+
+// TODO(b/251476085): remove along with Utils.CredentialType
+/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
+private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
+ when (this) {
+ Utils.CREDENTIAL_PIN -> PromptKind.PIN
+ Utils.CREDENTIAL_PASSWORD -> PromptKind.PASSWORD
+ Utils.CREDENTIAL_PATTERN -> PromptKind.PATTERN
+ else -> PromptKind.ANY_BIOMETRIC
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricOperationInfo.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricOperationInfo.kt
new file mode 100644
index 0000000..c619b12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricOperationInfo.kt
@@ -0,0 +1,4 @@
+package com.android.systemui.biometrics.domain.model
+
+/** Metadata about an in-progress biometric operation. */
+data class BiometricOperationInfo(val gatekeeperChallenge: Long = -1)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
new file mode 100644
index 0000000..5ee0381
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -0,0 +1,69 @@
+package com.android.systemui.biometrics.domain.model
+
+import android.hardware.biometrics.PromptInfo
+
+/**
+ * Preferences for BiometricPrompt, such as title & description, that are immutable while the prompt
+ * is showing.
+ *
+ * This roughly corresponds to a "request" by the system or an app to show BiometricPrompt and it
+ * contains a subset of the information in a [PromptInfo] that is relevant to SysUI.
+ */
+sealed class BiometricPromptRequest(
+ val title: String,
+ val subtitle: String,
+ val description: String,
+ val userInfo: BiometricUserInfo,
+ val operationInfo: BiometricOperationInfo,
+) {
+ /** Prompt using one or more biometrics. */
+ class Biometric(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ ) :
+ BiometricPromptRequest(
+ title = info.title?.toString() ?: "",
+ subtitle = info.subtitle?.toString() ?: "",
+ description = info.description?.toString() ?: "",
+ userInfo = userInfo,
+ operationInfo = operationInfo
+ )
+
+ /** Prompt using a credential (pin, pattern, password). */
+ sealed class Credential(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ ) :
+ BiometricPromptRequest(
+ title = (info.deviceCredentialTitle ?: info.title)?.toString() ?: "",
+ subtitle = (info.deviceCredentialSubtitle ?: info.subtitle)?.toString() ?: "",
+ description = (info.deviceCredentialDescription ?: info.description)?.toString() ?: "",
+ userInfo = userInfo,
+ operationInfo = operationInfo,
+ ) {
+
+ /** PIN prompt. */
+ class Pin(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ ) : Credential(info, userInfo, operationInfo)
+
+ /** Password prompt. */
+ class Password(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ ) : Credential(info, userInfo, operationInfo)
+
+ /** Pattern prompt. */
+ class Pattern(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ val stealthMode: Boolean,
+ ) : Credential(info, userInfo, operationInfo)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt
new file mode 100644
index 0000000..08da04d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt
@@ -0,0 +1,7 @@
+package com.android.systemui.biometrics.domain.model
+
+/** Metadata about the current user BiometricPrompt is being shown to. */
+data class BiometricUserInfo(
+ val userId: Int,
+ val deviceCredentialOwnerId: Int = userId,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
new file mode 100644
index 0000000..bcc0575
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -0,0 +1,130 @@
+package com.android.systemui.biometrics.ui
+
+import android.content.Context
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowInsets.Type.ime
+import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
+import android.widget.ImeAwareEditText
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.core.view.isGone
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.ui.binder.CredentialViewBinder
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+
+/** PIN or password credential view for BiometricPrompt. */
+class CredentialPasswordView(context: Context, attrs: AttributeSet?) :
+ LinearLayout(context, attrs), CredentialView, View.OnApplyWindowInsetsListener {
+
+ private lateinit var titleView: TextView
+ private lateinit var subtitleView: TextView
+ private lateinit var descriptionView: TextView
+ private lateinit var iconView: ImageView
+ private lateinit var passwordField: ImeAwareEditText
+ private lateinit var credentialHeader: View
+ private lateinit var credentialInput: View
+
+ private var bottomInset: Int = 0
+
+ private val accessibilityManager by lazy {
+ context.getSystemService(AccessibilityManager::class.java)
+ }
+
+ /** Initializes the view. */
+ override fun init(
+ viewModel: CredentialViewModel,
+ host: CredentialView.Host,
+ panelViewController: AuthPanelController,
+ animatePanel: Boolean,
+ ) {
+ CredentialViewBinder.bind(this, host, viewModel, panelViewController, animatePanel)
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+
+ titleView = requireViewById(R.id.title)
+ subtitleView = requireViewById(R.id.subtitle)
+ descriptionView = requireViewById(R.id.description)
+ iconView = requireViewById(R.id.icon)
+ subtitleView = requireViewById(R.id.subtitle)
+ passwordField = requireViewById(R.id.lockPassword)
+ credentialHeader = requireViewById(R.id.auth_credential_header)
+ credentialInput = requireViewById(R.id.auth_credential_input)
+
+ setOnApplyWindowInsetsListener(this)
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+
+ val inputLeftBound: Int
+ val inputTopBound: Int
+ var headerRightBound = right
+ var headerTopBounds = top
+ val subTitleBottom: Int = if (subtitleView.isGone) titleView.bottom else subtitleView.bottom
+ val descBottom = if (descriptionView.isGone) subTitleBottom else descriptionView.bottom
+ if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) {
+ inputTopBound = (bottom - credentialInput.height) / 2
+ inputLeftBound = (right - left) / 2
+ headerRightBound = inputLeftBound
+ headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset)
+ } else {
+ inputTopBound = descBottom + (bottom - descBottom - credentialInput.height) / 2
+ inputLeftBound = (right - left - credentialInput.width) / 2
+ }
+
+ if (descriptionView.bottom > bottomInset) {
+ credentialHeader.layout(left, headerTopBounds, headerRightBound, bottom)
+ }
+ credentialInput.layout(inputLeftBound, inputTopBound, right, bottom)
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+ val newWidth = MeasureSpec.getSize(widthMeasureSpec)
+ val newHeight = MeasureSpec.getSize(heightMeasureSpec) - bottomInset
+
+ setMeasuredDimension(newWidth, newHeight)
+
+ val halfWidthSpec = MeasureSpec.makeMeasureSpec(width / 2, MeasureSpec.AT_MOST)
+ val fullHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.UNSPECIFIED)
+ if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) {
+ measureChildren(halfWidthSpec, fullHeightSpec)
+ } else {
+ measureChildren(widthMeasureSpec, fullHeightSpec)
+ }
+ }
+
+ override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
+ val bottomInsets = insets.getInsets(ime())
+ if (bottomInset != bottomInsets.bottom) {
+ bottomInset = bottomInsets.bottom
+
+ if (bottomInset > 0 && resources.configuration.orientation == ORIENTATION_LANDSCAPE) {
+ titleView.isSingleLine = true
+ titleView.ellipsize = TextUtils.TruncateAt.MARQUEE
+ titleView.marqueeRepeatLimit = -1
+ // select to enable marquee unless a screen reader is enabled
+ titleView.isSelected = accessibilityManager.shouldMarquee()
+ } else {
+ titleView.isSingleLine = false
+ titleView.ellipsize = null
+ // select to enable marquee unless a screen reader is enabled
+ titleView.isSelected = false
+ }
+
+ requestLayout()
+ }
+ return insets
+ }
+}
+
+private fun AccessibilityManager.shouldMarquee(): Boolean = !isEnabled || !isTouchExplorationEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
new file mode 100644
index 0000000..75331f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
@@ -0,0 +1,23 @@
+package com.android.systemui.biometrics.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.ui.binder.CredentialViewBinder
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+
+/** Pattern credential view for BiometricPrompt. */
+class CredentialPatternView(context: Context, attrs: AttributeSet?) :
+ LinearLayout(context, attrs), CredentialView {
+
+ /** Initializes the view. */
+ override fun init(
+ viewModel: CredentialViewModel,
+ host: CredentialView.Host,
+ panelViewController: AuthPanelController,
+ animatePanel: Boolean,
+ ) {
+ CredentialViewBinder.bind(this, host, viewModel, panelViewController, animatePanel)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt
new file mode 100644
index 0000000..b7c6a45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt
@@ -0,0 +1,31 @@
+package com.android.systemui.biometrics.ui
+
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+
+/** A credential variant of BiometricPrompt. */
+sealed interface CredentialView {
+ /**
+ * Callbacks for the "host" container view that contains this credential view.
+ *
+ * TODO(b/251476085): Removed when the host view is converted to use a parent view model.
+ */
+ interface Host {
+ /** When the user's credential has been verified. */
+ fun onCredentialMatched(attestation: ByteArray)
+
+ /** When the user abandons credential verification. */
+ fun onCredentialAborted()
+
+ /** Warn the user is warned about excessive attempts. */
+ fun onCredentialAttemptsRemaining(remaining: Int, messageBody: String)
+ }
+
+ // TODO(251476085): remove AuthPanelController
+ fun init(
+ viewModel: CredentialViewModel,
+ host: Host,
+ panelViewController: AuthPanelController,
+ animatePanel: Boolean,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
new file mode 100644
index 0000000..c619648
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -0,0 +1,104 @@
+package com.android.systemui.biometrics.ui.binder
+
+import android.view.KeyEvent
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
+import android.widget.ImeAwareEditText
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.biometrics.ui.CredentialPasswordView
+import com.android.systemui.biometrics.ui.CredentialView
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Sub-binder for the [CredentialPasswordView]. */
+object CredentialPasswordViewBinder {
+
+ /** Bind the view. */
+ fun bind(
+ view: CredentialPasswordView,
+ host: CredentialView.Host,
+ viewModel: CredentialViewModel,
+ ) {
+ val imeManager = view.context.getSystemService(InputMethodManager::class.java)!!
+
+ val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword)
+
+ view.repeatWhenAttached {
+ passwordField.requestFocus()
+ passwordField.scheduleShowSoftInput()
+
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // observe credential validation attempts and submit/cancel buttons
+ launch {
+ viewModel.header.collect { header ->
+ passwordField.setTextOperationUser(header.user)
+ passwordField.setOnEditorActionListener(
+ OnImeSubmitListener { text ->
+ launch { viewModel.checkCredential(text, header) }
+ }
+ )
+ passwordField.setOnKeyListener(
+ OnBackButtonListener { host.onCredentialAborted() }
+ )
+ }
+ }
+
+ launch {
+ viewModel.inputFlags.collect { flags ->
+ flags?.let { passwordField.inputType = it }
+ }
+ }
+
+ // dismiss on a valid credential check
+ launch {
+ viewModel.validatedAttestation.collect { attestation ->
+ if (attestation != null) {
+ imeManager.hideSoftInputFromWindow(view.windowToken, 0 /* flags */)
+ host.onCredentialMatched(attestation)
+ } else {
+ passwordField.setText("")
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+private class OnBackButtonListener(private val onBack: () -> Unit) : View.OnKeyListener {
+ override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
+ if (keyCode != KeyEvent.KEYCODE_BACK) {
+ return false
+ }
+ if (event.action == KeyEvent.ACTION_UP) {
+ onBack()
+ }
+ return true
+ }
+}
+
+private class OnImeSubmitListener(private val onSubmit: (text: CharSequence) -> Unit) :
+ TextView.OnEditorActionListener {
+ override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent?): Boolean {
+ val isSoftImeEvent =
+ event == null &&
+ (actionId == EditorInfo.IME_NULL ||
+ actionId == EditorInfo.IME_ACTION_DONE ||
+ actionId == EditorInfo.IME_ACTION_NEXT)
+ val isKeyboardEnterKey =
+ event != null &&
+ KeyEvent.isConfirmKey(event.keyCode) &&
+ event.action == KeyEvent.ACTION_DOWN
+ if (isSoftImeEvent || isKeyboardEnterKey) {
+ onSubmit(v.text)
+ return true
+ }
+ return false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
new file mode 100644
index 0000000..4765551
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
@@ -0,0 +1,75 @@
+package com.android.systemui.biometrics.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternView
+import com.android.systemui.R
+import com.android.systemui.biometrics.ui.CredentialPatternView
+import com.android.systemui.biometrics.ui.CredentialView
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Sub-binder for the [CredentialPatternView]. */
+object CredentialPatternViewBinder {
+
+ /** Bind the view. */
+ fun bind(
+ view: CredentialPatternView,
+ host: CredentialView.Host,
+ viewModel: CredentialViewModel,
+ ) {
+ val lockPatternView: LockPatternView = view.requireViewById(R.id.lockPattern)
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // observe credential validation attempts and submit/cancel buttons
+ launch {
+ viewModel.header.collect { header ->
+ lockPatternView.setOnPatternListener(
+ OnPatternDetectedListener { pattern ->
+ if (pattern.isPatternLongEnough()) {
+ // Pattern size is less than the minimum
+ // do not count it as a failed attempt
+ viewModel.showPatternTooShortError()
+ } else {
+ lockPatternView.isEnabled = false
+ launch { viewModel.checkCredential(pattern, header) }
+ }
+ }
+ )
+ }
+ }
+
+ launch { viewModel.stealthMode.collect { lockPatternView.isInStealthMode = it } }
+
+ // dismiss on a valid credential check
+ launch {
+ viewModel.validatedAttestation.collect { attestation ->
+ val matched = attestation != null
+ lockPatternView.isEnabled = !matched
+ if (matched) {
+ host.onCredentialMatched(attestation!!)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+private class OnPatternDetectedListener(
+ private val onDetected: (pattern: List<LockPatternView.Cell>) -> Unit
+) : LockPatternView.OnPatternListener {
+ override fun onPatternCellAdded(pattern: List<LockPatternView.Cell>) {}
+ override fun onPatternCleared() {}
+ override fun onPatternStart() {}
+ override fun onPatternDetected(pattern: List<LockPatternView.Cell>) {
+ onDetected(pattern)
+ }
+}
+
+private fun List<LockPatternView.Cell>.isPatternLongEnough(): Boolean =
+ size < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
new file mode 100644
index 0000000..fcc9487
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -0,0 +1,140 @@
+package com.android.systemui.biometrics.ui.binder
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.biometrics.AuthDialog
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.ui.CredentialPasswordView
+import com.android.systemui.biometrics.ui.CredentialPatternView
+import com.android.systemui.biometrics.ui.CredentialView
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+/**
+ * View binder for all credential variants of BiometricPrompt, including [CredentialPatternView] and
+ * [CredentialPasswordView].
+ *
+ * This binder delegates to sub-binders for each variant, such as the [CredentialPasswordViewBinder]
+ * and [CredentialPatternViewBinder].
+ */
+object CredentialViewBinder {
+
+ /** Binds a [CredentialPasswordView] or [CredentialPatternView] to a [CredentialViewModel]. */
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ host: CredentialView.Host,
+ viewModel: CredentialViewModel,
+ panelViewController: AuthPanelController,
+ animatePanel: Boolean,
+ maxErrorDuration: Long = 3_000L,
+ ) {
+ val titleView: TextView = view.requireViewById(R.id.title)
+ val subtitleView: TextView = view.requireViewById(R.id.subtitle)
+ val descriptionView: TextView = view.requireViewById(R.id.description)
+ val iconView: ImageView? = view.findViewById(R.id.icon)
+ val errorView: TextView = view.requireViewById(R.id.error)
+
+ var errorTimer: Job? = null
+
+ // bind common elements
+ view.repeatWhenAttached {
+ if (animatePanel) {
+ with(panelViewController) {
+ // Credential view is always full screen.
+ setUseFullScreen(true)
+ updateForContentDimensions(
+ containerWidth,
+ containerHeight,
+ 0 /* animateDurationMs */
+ )
+ }
+ }
+
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // show prompt metadata
+ launch {
+ viewModel.header.collect { header ->
+ titleView.text = header.title
+ view.announceForAccessibility(header.title)
+
+ subtitleView.textOrHide = header.subtitle
+ descriptionView.textOrHide = header.description
+
+ iconView?.setImageDrawable(header.icon)
+
+ // Only animate this if we're transitioning from a biometric view.
+ if (viewModel.animateContents.value) {
+ view.animateCredentialViewIn()
+ }
+ }
+ }
+
+ // show transient error messages
+ launch {
+ viewModel.errorMessage
+ .onEach { msg ->
+ errorTimer?.cancel()
+ if (msg.isNotBlank()) {
+ errorTimer = launch {
+ delay(maxErrorDuration)
+ viewModel.resetErrorMessage()
+ }
+ }
+ }
+ .collect { errorView.textOrHide = it }
+ }
+
+ // show an extra dialog if the remaining attempts becomes low
+ launch {
+ viewModel.remainingAttempts
+ .filter { it.remaining != null }
+ .collect { info ->
+ host.onCredentialAttemptsRemaining(info.remaining!!, info.message)
+ }
+ }
+ }
+ }
+
+ // bind the auth widget
+ when (view) {
+ is CredentialPasswordView -> CredentialPasswordViewBinder.bind(view, host, viewModel)
+ is CredentialPatternView -> CredentialPatternViewBinder.bind(view, host, viewModel)
+ else -> throw IllegalStateException("unexpected view type: ${view.javaClass.name}")
+ }
+ }
+}
+
+private fun View.animateCredentialViewIn() {
+ translationY = resources.getDimension(R.dimen.biometric_dialog_credential_translation_offset)
+ alpha = 0f
+ postOnAnimation {
+ animate()
+ .translationY(0f)
+ .setDuration(AuthDialog.ANIMATE_CREDENTIAL_INITIAL_DURATION_MS.toLong())
+ .alpha(1f)
+ .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .withLayer()
+ .start()
+ }
+}
+
+private var TextView.textOrHide: String?
+ set(value) {
+ val gone = value.isNullOrBlank()
+ visibility = if (gone) View.GONE else View.VISIBLE
+ text = if (gone) "" else value
+ }
+ get() = text?.toString()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
new file mode 100644
index 0000000..84bbceb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
@@ -0,0 +1,178 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.text.InputType
+import com.android.internal.widget.LockPatternView
+import com.android.systemui.R
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.CredentialStatus
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
+
+/** View-model for all CredentialViews within BiometricPrompt. */
+class CredentialViewModel
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val credentialInteractor: BiometricPromptCredentialInteractor,
+) {
+
+ /** Top level information about the prompt. */
+ val header: Flow<HeaderViewModel> =
+ credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>().map {
+ request ->
+ BiometricPromptHeaderViewModelImpl(
+ request,
+ user = UserHandle.of(request.userInfo.userId),
+ title = request.title,
+ subtitle = request.subtitle,
+ description = request.description,
+ icon = applicationContext.asLockIcon(request.userInfo.deviceCredentialOwnerId),
+ )
+ }
+
+ /** Input flags for text based credential views */
+ val inputFlags: Flow<Int?> =
+ credentialInteractor.prompt.map {
+ when (it) {
+ is BiometricPromptRequest.Credential.Pin ->
+ InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
+ else -> null
+ }
+ }
+
+ /** If stealth mode is active (hide user credential input). */
+ val stealthMode: Flow<Boolean> =
+ credentialInteractor.prompt.map {
+ when (it) {
+ is BiometricPromptRequest.Credential.Pattern -> it.stealthMode
+ else -> false
+ }
+ }
+
+ private val _animateContents: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ /** If this view should be animated on transitions. */
+ val animateContents = _animateContents.asStateFlow()
+
+ /** Error messages to show the user. */
+ val errorMessage: Flow<String> =
+ combine(credentialInteractor.verificationError, credentialInteractor.prompt) { error, p ->
+ when (error) {
+ is CredentialStatus.Fail.Error -> error.error
+ ?: applicationContext.asBadCredentialErrorMessage(p)
+ is CredentialStatus.Fail.Throttled -> error.error
+ null -> ""
+ }
+ }
+
+ private val _validatedAttestation: MutableSharedFlow<ByteArray?> = MutableSharedFlow()
+ /** Results of [checkPatternCredential]. A non-null attestation is supplied on success. */
+ val validatedAttestation: Flow<ByteArray?> = _validatedAttestation.asSharedFlow()
+
+ private val _remainingAttempts: MutableStateFlow<RemainingAttempts> =
+ MutableStateFlow(RemainingAttempts())
+ /** If set, the number of remaining attempts before the user must stop. */
+ val remainingAttempts: Flow<RemainingAttempts> = _remainingAttempts.asStateFlow()
+
+ /** Enable transition animations. */
+ fun setAnimateContents(animate: Boolean) {
+ _animateContents.value = animate
+ }
+
+ /** Show an error message to inform the user the pattern is too short to attempt validation. */
+ fun showPatternTooShortError() {
+ credentialInteractor.setVerificationError(
+ CredentialStatus.Fail.Error(
+ applicationContext.asBadCredentialErrorMessage(
+ BiometricPromptRequest.Credential.Pattern::class
+ )
+ )
+ )
+ }
+
+ /** Reset the error message to an empty string. */
+ fun resetErrorMessage() {
+ credentialInteractor.resetVerificationError()
+ }
+
+ /** Check a PIN or password and update [validatedAttestation] or [remainingAttempts]. */
+ suspend fun checkCredential(text: CharSequence, header: HeaderViewModel) =
+ checkCredential(credentialInteractor.checkCredential(header.asRequest(), text = text))
+
+ /** Check a pattern and update [validatedAttestation] or [remainingAttempts]. */
+ suspend fun checkCredential(pattern: List<LockPatternView.Cell>, header: HeaderViewModel) =
+ checkCredential(credentialInteractor.checkCredential(header.asRequest(), pattern = pattern))
+
+ private suspend fun checkCredential(result: CredentialStatus) {
+ when (result) {
+ is CredentialStatus.Success.Verified -> {
+ _validatedAttestation.emit(result.hat)
+ _remainingAttempts.value = RemainingAttempts()
+ }
+ is CredentialStatus.Fail.Error -> {
+ _validatedAttestation.emit(null)
+ _remainingAttempts.value =
+ RemainingAttempts(result.remainingAttempts, result.urgentMessage ?: "")
+ }
+ is CredentialStatus.Fail.Throttled -> {
+ // required for completeness, but a throttled error cannot be the final result
+ _validatedAttestation.emit(null)
+ _remainingAttempts.value = RemainingAttempts()
+ }
+ }
+ }
+}
+
+private fun Context.asBadCredentialErrorMessage(prompt: BiometricPromptRequest?): String =
+ asBadCredentialErrorMessage(
+ if (prompt != null) prompt::class else BiometricPromptRequest.Credential.Password::class
+ )
+
+private fun <T : BiometricPromptRequest> Context.asBadCredentialErrorMessage(
+ clazz: KClass<T>
+): String =
+ getString(
+ when (clazz) {
+ BiometricPromptRequest.Credential.Pin::class -> R.string.biometric_dialog_wrong_pin
+ BiometricPromptRequest.Credential.Password::class ->
+ R.string.biometric_dialog_wrong_password
+ BiometricPromptRequest.Credential.Pattern::class ->
+ R.string.biometric_dialog_wrong_pattern
+ else -> R.string.biometric_dialog_wrong_password
+ }
+ )
+
+private fun Context.asLockIcon(userId: Int): Drawable {
+ val id =
+ if (Utils.isManagedProfile(this, userId)) {
+ R.drawable.auth_dialog_enterprise
+ } else {
+ R.drawable.auth_dialog_lock
+ }
+ return resources.getDrawable(id, theme)
+}
+
+private class BiometricPromptHeaderViewModelImpl(
+ val request: BiometricPromptRequest.Credential,
+ override val user: UserHandle,
+ override val title: String,
+ override val subtitle: String,
+ override val description: String,
+ override val icon: Drawable,
+) : HeaderViewModel
+
+private fun HeaderViewModel.asRequest(): BiometricPromptRequest.Credential =
+ (this as BiometricPromptHeaderViewModelImpl).request
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt
new file mode 100644
index 0000000..ba23f1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt
@@ -0,0 +1,13 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+
+/** View model for the top-level header / info area of BiometricPrompt. */
+interface HeaderViewModel {
+ val user: UserHandle
+ val title: String
+ val subtitle: String
+ val description: String
+ val icon: Drawable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/RemainingAttempts.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/RemainingAttempts.kt
new file mode 100644
index 0000000..0f22173
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/RemainingAttempts.kt
@@ -0,0 +1,4 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+/** Metadata about the number of credential attempts the user has left [remaining], if known. */
+data class RemainingAttempts(val remaining: Int? = null, val message: String = "")
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7e31626..e47e636 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -48,6 +48,7 @@
import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent;
+import com.android.systemui.notetask.NoteTaskModule;
import com.android.systemui.people.PeopleModule;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import com.android.systemui.privacy.PrivacyModule;
@@ -152,6 +153,7 @@
TunerModule.class,
UserModule.class,
UtilModule.class,
+ NoteTaskModule.class,
WalletModule.class
},
subcomponents = {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 29bb2f4..41f5578 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -164,7 +164,8 @@
COMPLICATION_TYPE_AIR_QUALITY,
COMPLICATION_TYPE_CAST_INFO,
COMPLICATION_TYPE_HOME_CONTROLS,
- COMPLICATION_TYPE_SMARTSPACE
+ COMPLICATION_TYPE_SMARTSPACE,
+ COMPLICATION_TYPE_MEDIA_ENTRY
})
@Retention(RetentionPolicy.SOURCE)
@interface ComplicationType {}
@@ -177,6 +178,7 @@
int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
int COMPLICATION_TYPE_HOME_CONTROLS = 1 << 5;
int COMPLICATION_TYPE_SMARTSPACE = 1 << 6;
+ int COMPLICATION_TYPE_MEDIA_ENTRY = 1 << 7;
/**
* The {@link Host} interface specifies a way a {@link Complication} to communicate with its
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
index 75a97de..18aacd2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
@@ -20,6 +20,7 @@
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_MEDIA_ENTRY;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_SMARTSPACE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
@@ -54,6 +55,8 @@
return COMPLICATION_TYPE_HOME_CONTROLS;
case DreamBackend.COMPLICATION_TYPE_SMARTSPACE:
return COMPLICATION_TYPE_SMARTSPACE;
+ case DreamBackend.COMPLICATION_TYPE_MEDIA_ENTRY:
+ return COMPLICATION_TYPE_MEDIA_ENTRY;
default:
return COMPLICATION_TYPE_NONE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
index 1166c2f..deff060 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
@@ -55,6 +55,11 @@
return mComponentFactory.create().getViewHolder();
}
+ @Override
+ public int getRequiredTypeAvailability() {
+ return COMPLICATION_TYPE_MEDIA_ENTRY;
+ }
+
/**
* Contains values/logic associated with the dream complication view.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 3adeeac..1f061e9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -21,6 +21,7 @@
import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS;
import static com.android.systemui.flags.FlagManager.EXTRA_ID;
import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
import static java.util.Objects.requireNonNull;
@@ -59,20 +60,20 @@
*
* Flags can be set (or unset) via the following adb command:
*
- * adb shell cmd statusbar flag <id> <on|off|toggle|erase>
+ * adb shell cmd statusbar flag <id> <on|off|toggle|erase>
*
- * Alternatively, you can change flags via a broadcast intent:
+ * Alternatively, you can change flags via a broadcast intent:
*
- * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
+ * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
*
* To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
*/
@SysUISingleton
public class FeatureFlagsDebug implements FeatureFlags {
static final String TAG = "SysUIFlags";
- static final String ALL_FLAGS = "all_flags";
private final FlagManager mFlagManager;
+ private final Context mContext;
private final SecureSettings mSecureSettings;
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
@@ -83,6 +84,14 @@
private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
private final Restarter mRestarter;
+ private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
+ new ServerFlagReader.ChangeListener() {
+ @Override
+ public void onChange() {
+ mRestarter.restart();
+ }
+ };
+
@Inject
public FeatureFlagsDebug(
FlagManager flagManager,
@@ -93,23 +102,28 @@
DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
- Restarter barService) {
+ Restarter restarter) {
mFlagManager = flagManager;
+ mContext = context;
mSecureSettings = secureSettings;
mResources = resources;
mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
- mRestarter = barService;
+ mRestarter = restarter;
+ }
+ /** Call after construction to setup listeners. */
+ void init() {
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SET_FLAG);
filter.addAction(ACTION_GET_FLAGS);
- flagManager.setOnSettingsChangedAction(this::restartSystemUI);
- flagManager.setClearCacheAction(this::removeFromCache);
- context.registerReceiver(mReceiver, filter, null, null,
+ mFlagManager.setOnSettingsChangedAction(this::restartSystemUI);
+ mFlagManager.setClearCacheAction(this::removeFromCache);
+ mContext.registerReceiver(mReceiver, filter, null, null,
Context.RECEIVER_EXPORTED_UNAUDITED);
+ mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
}
@Override
@@ -196,7 +210,7 @@
return mStringFlagCache.get(id);
}
- /** Specific override for Boolean flags that checks against the teamfood list.*/
+ /** Specific override for Boolean flags that checks against the teamfood list. */
private boolean readFlagValue(int id, boolean defaultValue) {
Boolean result = readBooleanFlagOverride(id);
boolean hasServerOverride = mServerFlagReader.hasOverride(id);
@@ -273,6 +287,7 @@
private void dispatchListenersAndMaybeRestart(int id, Consumer<Boolean> restartAction) {
mFlagManager.dispatchListenersAndMaybeRestart(id, restartAction);
}
+
/** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
private void eraseInternal(int id) {
// We can't actually "erase" things from sysprops, but we can set them to empty!
@@ -358,7 +373,7 @@
}
}
- Bundle extras = getResultExtras(true);
+ Bundle extras = getResultExtras(true);
if (extras != null) {
extras.putParcelableArrayList(EXTRA_FLAGS, pFlags);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 560dcbd..6271334 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -31,7 +31,7 @@
dumpManager: DumpManager,
private val commandRegistry: CommandRegistry,
private val flagCommand: FlagCommand,
- featureFlags: FeatureFlags
+ private val featureFlags: FeatureFlagsDebug
) : CoreStartable {
init {
@@ -41,6 +41,7 @@
}
override fun start() {
+ featureFlags.init()
commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 40a8a1a..30cad5f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -16,6 +16,8 @@
package com.android.systemui.flags;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
+
import static java.util.Objects.requireNonNull;
import android.content.res.Resources;
@@ -34,6 +36,7 @@
import java.util.Map;
import javax.inject.Inject;
+import javax.inject.Named;
/**
* Default implementation of the a Flag manager that returns default values for release builds
@@ -49,26 +52,47 @@
private final SystemPropertiesHelper mSystemProperties;
private final DeviceConfigProxy mDeviceConfigProxy;
private final ServerFlagReader mServerFlagReader;
+ private final Restarter mRestarter;
+ private final Map<Integer, Flag<?>> mAllFlags;
SparseBooleanArray mBooleanCache = new SparseBooleanArray();
SparseArray<String> mStringCache = new SparseArray<>();
+ private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
+ new ServerFlagReader.ChangeListener() {
+ @Override
+ public void onChange() {
+ mRestarter.restart();
+ }
+ };
+
@Inject
public FeatureFlagsRelease(
@Main Resources resources,
SystemPropertiesHelper systemProperties,
DeviceConfigProxy deviceConfigProxy,
- ServerFlagReader serverFlagReader) {
+ ServerFlagReader serverFlagReader,
+ @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
+ Restarter restarter) {
mResources = resources;
mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
+ mAllFlags = allFlags;
+ mRestarter = restarter;
+ }
+
+ /** Call after construction to setup listeners. */
+ void init() {
+ mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
}
@Override
- public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {}
+ public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {
+ }
@Override
- public void removeListener(@NonNull Listener listener) {}
+ public void removeListener(@NonNull Listener listener) {
+ }
@Override
public boolean isEnabled(@NotNull UnreleasedFlag flag) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index 4d25431..1e93c0b7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -16,6 +16,8 @@
package com.android.systemui.flags;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
+
import androidx.annotation.NonNull;
import com.android.systemui.statusbar.commandline.Command;
@@ -42,7 +44,7 @@
@Inject
FlagCommand(
FeatureFlagsDebug featureFlags,
- @Named(FeatureFlagsDebug.ALL_FLAGS) Map<Integer, Flag<?>> allFlags
+ @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags
) {
mFeatureFlags = featureFlags;
mAllFlags = allFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 3620ba7..4818bcc 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -57,7 +57,7 @@
val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true)
// TODO(b/254512425): Tracking Bug
- val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = false)
+ val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = true)
// TODO(b/254512731): Tracking Bug
@JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
@@ -118,6 +118,12 @@
*/
@JvmField val STEP_CLOCK_ANIMATION = UnreleasedFlag(212)
+ /**
+ * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
+ * will occur in stages. This is one stage of many to come.
+ */
+ @JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213, teamfood = true)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = ReleasedFlag(300)
@@ -152,13 +158,13 @@
// TODO(b/254512678): Tracking Bug
@JvmField val NEW_FOOTER_ACTIONS = ReleasedFlag(507)
+ // TODO(b/244064524): Tracking Bug
+ @JvmField val QS_SECONDARY_DATA_SUB_INFO = UnreleasedFlag(508, teamfood = true)
+
// 600- status bar
// TODO(b/254513246): Tracking Bug
val STATUS_BAR_USER_SWITCHER = ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip)
- // TODO(b/254513025): Tracking Bug
- val STATUS_BAR_LETTERBOX_APPEARANCE = ReleasedFlag(603, teamfood = false)
-
// TODO(b/254512623): Tracking Bug
@Deprecated("Replaced by mobile and wifi specific flags.")
val NEW_STATUS_BAR_PIPELINE_BACKEND = UnreleasedFlag(604, teamfood = false)
@@ -325,6 +331,9 @@
// 1800 - shade container
@JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, true)
+ // 1900 - note task
+ @JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
new file mode 100644
index 0000000..e1f4944
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import com.android.internal.statusbar.IStatusBarService
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+/** Module containing shared code for all FeatureFlag implementations. */
+@Module
+interface FlagsCommonModule {
+ companion object {
+ const val ALL_FLAGS = "all_flags"
+
+ @JvmStatic
+ @Provides
+ @Named(ALL_FLAGS)
+ fun providesAllFlags(): Map<Int, Flag<*>> {
+ return Flags.collectFlags()
+ }
+
+ @JvmStatic
+ @Provides
+ fun providesRestarter(barService: IStatusBarService): Restarter {
+ return object : Restarter {
+ override fun restart() {
+ barService.restart()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index fc5b9f4..694fa01 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -16,9 +16,13 @@
package com.android.systemui.flags
+import android.provider.DeviceConfig
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.DeviceConfigProxy
-import dagger.Binds
import dagger.Module
+import dagger.Provides
+import java.util.concurrent.Executor
import javax.inject.Inject
interface ServerFlagReader {
@@ -27,40 +31,99 @@
/** Returns any stored server-side setting or the default if not set. */
fun readServerOverride(flagId: Int, default: Boolean): Boolean
+
+ /** Register a listener for changes to any of the passed in flags. */
+ fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener)
+
+ interface ChangeListener {
+ fun onChange()
+ }
}
class ServerFlagReaderImpl @Inject constructor(
- private val deviceConfig: DeviceConfigProxy
+ private val namespace: String,
+ private val deviceConfig: DeviceConfigProxy,
+ @Background private val executor: Executor
) : ServerFlagReader {
+
+ private val listeners =
+ mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
+
+ private val onPropertiesChangedListener = object : DeviceConfig.OnPropertiesChangedListener {
+ override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
+ if (properties.namespace != namespace) {
+ return
+ }
+
+ for ((listener, flags) in listeners) {
+ propLoop@ for (propName in properties.keyset) {
+ for (flag in flags) {
+ if (propName == getServerOverrideName(flag.id)) {
+ listener.onChange()
+ break@propLoop
+ }
+ }
+ }
+ }
+ }
+ }
+
override fun hasOverride(flagId: Int): Boolean =
deviceConfig.getProperty(
- SYSUI_NAMESPACE,
+ namespace,
getServerOverrideName(flagId)
) != null
override fun readServerOverride(flagId: Int, default: Boolean): Boolean {
return deviceConfig.getBoolean(
- SYSUI_NAMESPACE,
+ namespace,
getServerOverrideName(flagId),
default
)
}
+ override fun listenForChanges(
+ flags: Collection<Flag<*>>,
+ listener: ServerFlagReader.ChangeListener
+ ) {
+ if (listeners.isEmpty()) {
+ deviceConfig.addOnPropertiesChangedListener(
+ namespace,
+ executor,
+ onPropertiesChangedListener
+ )
+ }
+ listeners.add(Pair(listener, flags))
+ }
+
private fun getServerOverrideName(flagId: Int): String {
return "flag_override_$flagId"
}
}
-private val SYSUI_NAMESPACE = "systemui"
-
@Module
interface ServerFlagReaderModule {
- @Binds
- fun bindsReader(impl: ServerFlagReaderImpl): ServerFlagReader
+ companion object {
+ private val SYSUI_NAMESPACE = "systemui"
+
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun bindsReader(
+ deviceConfig: DeviceConfigProxy,
+ @Background executor: Executor
+ ): ServerFlagReader {
+ return ServerFlagReaderImpl(
+ SYSUI_NAMESPACE, deviceConfig, executor
+ )
+ }
+ }
}
class ServerFlagReaderFake : ServerFlagReader {
private val flagMap: MutableMap<Int, Boolean> = mutableMapOf()
+ private val listeners =
+ mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
override fun hasOverride(flagId: Int): Boolean {
return flagMap.containsKey(flagId)
@@ -72,9 +135,24 @@
fun setFlagValue(flagId: Int, value: Boolean) {
flagMap.put(flagId, value)
+
+ for ((listener, flags) in listeners) {
+ flagLoop@ for (flag in flags) {
+ if (flagId == flag.id) {
+ listener.onChange()
+ break@flagLoop
+ }
+ }
+ }
}
fun eraseFlag(flagId: Int) {
flagMap.remove(flagId)
}
+
+ override fun listenForChanges(
+ flags: Collection<Flag<*>>,
+ listener: ServerFlagReader.ChangeListener
+ ) {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 1c6cec2..0214313 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -518,7 +518,7 @@
@PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp");
checkPermission();
- mKeyguardViewMediator.onStartedWakingUp(cameraGestureTriggered);
+ mKeyguardViewMediator.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
mKeyguardLifecyclesDispatcher.dispatch(
KeyguardLifecyclesDispatcher.STARTED_WAKING_UP, pmWakeReason);
Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 0d74dc8..41abb62 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -91,6 +91,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
@@ -322,6 +323,12 @@
// true if the keyguard is hidden by another window
private boolean mOccluded = false;
+ /**
+ * Whether the {@link #mOccludeAnimationController} is currently playing the occlusion
+ * animation.
+ */
+ private boolean mOccludeAnimationPlaying = false;
+
private boolean mWakeAndUnlocking = false;
/**
@@ -335,12 +342,6 @@
*/
private int mDelayedProfileShowingSequence;
- /**
- * If the user has disabled the keyguard, then requests to exit, this is
- * how we'll ultimately let them know whether it was successful. We use this
- * var being non-null as an indicator that there is an in progress request.
- */
- private IKeyguardExitCallback mExitSecureCallback;
private final DismissCallbackRegistry mDismissCallbackRegistry;
// the properties of the keyguard
@@ -837,15 +838,22 @@
/**
* Animation launch controller for activities that occlude the keyguard.
*/
- private final ActivityLaunchAnimator.Controller mOccludeAnimationController =
+ @VisibleForTesting
+ final ActivityLaunchAnimator.Controller mOccludeAnimationController =
new ActivityLaunchAnimator.Controller() {
@Override
- public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {}
+ public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
+ mOccludeAnimationPlaying = true;
+ }
@Override
public void onLaunchAnimationCancelled(@Nullable Boolean newKeyguardOccludedState) {
Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: "
+ mOccluded);
+ mOccludeAnimationPlaying = false;
+
+ // Ensure keyguard state is set correctly if we're cancelled.
+ mCentralSurfaces.updateIsKeyguard();
}
@Override
@@ -854,6 +862,12 @@
mCentralSurfaces.instantCollapseNotificationPanel();
}
+ mOccludeAnimationPlaying = false;
+
+ // Hide the keyguard now that we're done launching the occluding activity over
+ // it.
+ mCentralSurfaces.updateIsKeyguard();
+
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
}
@@ -1322,18 +1336,7 @@
|| !mLockPatternUtils.isSecure(currentUser);
long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
mLockLater = false;
- if (mExitSecureCallback != null) {
- if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled");
- try {
- mExitSecureCallback.onKeyguardExitResult(false);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
- }
- mExitSecureCallback = null;
- if (!mExternallyEnabled) {
- hideLocked();
- }
- } else if (mShowing && !mKeyguardStateController.isKeyguardGoingAway()) {
+ if (mShowing && !mKeyguardStateController.isKeyguardGoingAway()) {
// If we are going to sleep but the keyguard is showing (and will continue to be
// showing, not in the process of going away) then reset its state. Otherwise, let
// this fall through and explicitly re-lock the keyguard.
@@ -1575,7 +1578,8 @@
/**
* It will let us know when the device is waking up.
*/
- public void onStartedWakingUp(boolean cameraGestureTriggered) {
+ public void onStartedWakingUp(@PowerManager.WakeReason int pmWakeReason,
+ boolean cameraGestureTriggered) {
Trace.beginSection("KeyguardViewMediator#onStartedWakingUp");
// TODO: Rename all screen off/on references to interactive/sleeping
@@ -1590,7 +1594,7 @@
if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence);
notifyStartedWakingUp();
}
- mUpdateMonitor.dispatchStartedWakingUp();
+ mUpdateMonitor.dispatchStartedWakingUp(pmWakeReason);
maybeSendUserPresentBroadcast();
Trace.endSection();
}
@@ -1652,13 +1656,6 @@
mExternallyEnabled = enabled;
if (!enabled && mShowing) {
- if (mExitSecureCallback != null) {
- if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
- // we're in the process of handling a request to verify the user
- // can get past the keyguard. ignore extraneous requests to disable / re-enable
- return;
- }
-
// hiding keyguard that is showing, remember to reshow later
if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, "
+ "disabling status bar expansion");
@@ -1672,33 +1669,23 @@
mNeedToReshowWhenReenabled = false;
updateInputRestrictedLocked();
- if (mExitSecureCallback != null) {
- if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting");
- try {
- mExitSecureCallback.onKeyguardExitResult(false);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
- }
- mExitSecureCallback = null;
- resetStateLocked();
- } else {
- showLocked(null);
+ showLocked(null);
- // block until we know the keyguard is done drawing (and post a message
- // to unblock us after a timeout, so we don't risk blocking too long
- // and causing an ANR).
- mWaitingUntilKeyguardVisible = true;
- mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
- if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false");
- while (mWaitingUntilKeyguardVisible) {
- try {
- wait();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
+ // block until we know the keyguard is done drawing (and post a message
+ // to unblock us after a timeout, so we don't risk blocking too long
+ // and causing an ANR).
+ mWaitingUntilKeyguardVisible = true;
+ mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING,
+ KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
+ if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false");
+ while (mWaitingUntilKeyguardVisible) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
}
- if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible");
}
+ if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible");
}
}
}
@@ -1728,13 +1715,6 @@
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
}
- } else if (mExitSecureCallback != null) {
- // already in progress with someone else
- try {
- callback.onKeyguardExitResult(false);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
- }
} else if (!isSecure()) {
// Keyguard is not secure, no need to do anything, and we don't need to reshow
@@ -1768,6 +1748,10 @@
return mShowing && !mOccluded;
}
+ public boolean isOccludeAnimationPlaying() {
+ return mOccludeAnimationPlaying;
+ }
+
/**
* Notify us when the keyguard is occluded by another window
*/
@@ -2265,21 +2249,6 @@
return;
}
setPendingLock(false); // user may have authenticated during the screen off animation
- if (mExitSecureCallback != null) {
- try {
- mExitSecureCallback.onKeyguardExitResult(true /* authenciated */);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult()", e);
- }
-
- mExitSecureCallback = null;
-
- // after successfully exiting securely, no need to reshow
- // the keyguard when they've released the lock
- mExternallyEnabled = true;
- mNeedToReshowWhenReenabled = false;
- updateInputRestricted();
- }
handleHide();
mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
@@ -3087,7 +3056,6 @@
pw.print(" mInputRestricted: "); pw.println(mInputRestricted);
pw.print(" mOccluded: "); pw.println(mOccluded);
pw.print(" mDelayedShowingSequence: "); pw.println(mDelayedShowingSequence);
- pw.print(" mExitSecureCallback: "); pw.println(mExitSecureCallback);
pw.print(" mDeviceInteractive: "); pw.println(mDeviceInteractive);
pw.print(" mGoingToSleep: "); pw.println(mGoingToSleep);
pw.print(" mHiding: "); pw.println(mHiding);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
new file mode 100644
index 0000000..a069582
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+/**
+ * Unique identifier keys for all known built-in quick affordances.
+ *
+ * Please ensure uniqueness by never associating more than one class with each key.
+ */
+object BuiltInKeyguardQuickAffordanceKeys {
+ // Please keep alphabetical order of const names to simplify future maintenance.
+ const val HOME_CONTROLS = "home"
+ const val QR_CODE_SCANNER = "qr_code_scanner"
+ const val QUICK_ACCESS_WALLET = "wallet"
+ // Please keep alphabetical order of const names to simplify future maintenance.
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 8384260..d3bb34c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
import android.content.Intent
@@ -51,6 +51,8 @@
private val appContext = context.applicationContext
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+
override val state: Flow<KeyguardQuickAffordanceConfig.State> =
component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
if (canShowWhileLocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 95027d0..0dd0ad7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.content.Intent
import com.android.systemui.animation.Expandable
@@ -26,8 +26,18 @@
/** Defines interface that can act as data source for a single quick affordance model. */
interface KeyguardQuickAffordanceConfig {
+ /** Unique identifier for this quick affordance. It must be globally unique. */
+ val key: String
+
+ /** The observable [State] of the affordance. */
val state: Flow<State>
+ /**
+ * Notifies that the affordance was clicked by the user.
+ *
+ * @param expandable An [Expandable] to use when animating dialogs or activities
+ * @return An [OnClickedResult] telling the caller what to do next
+ */
fun onQuickAffordanceClicked(expandable: Expandable?): OnClickedResult
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index 502a607..9a44139 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import com.android.systemui.R
import com.android.systemui.animation.Expandable
@@ -37,6 +37,8 @@
private val controller: QRCodeScannerController,
) : KeyguardQuickAffordanceConfig {
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+
override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
val callback =
object : QRCodeScannerController.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index a24a0d6..8a1267e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsError
@@ -44,6 +44,8 @@
private val activityStarter: ActivityStarter,
) : KeyguardQuickAffordanceConfig {
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+
override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
val callback =
object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index e8532ec..ab25597 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -20,6 +20,7 @@
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.annotation.FloatRange
+import android.os.Trace
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -157,12 +158,36 @@
value: Float,
transitionState: TransitionState
) {
+ trace(info, transitionState)
+
if (transitionState == TransitionState.FINISHED) {
currentTransitionInfo = null
}
_transitions.value = TransitionStep(info.from, info.to, value, transitionState)
}
+ private fun trace(info: TransitionInfo, transitionState: TransitionState) {
+ if (
+ transitionState != TransitionState.STARTED &&
+ transitionState != TransitionState.FINISHED
+ ) {
+ return
+ }
+ val traceName =
+ "Transition: ${info.from} -> ${info.to} " +
+ if (info.animator == null) {
+ "(manual)"
+ } else {
+ ""
+ }
+ val traceCookie = traceName.hashCode()
+ if (transitionState == TransitionState.STARTED) {
+ Trace.beginAsyncSection(traceName, traceCookie)
+ } else if (transitionState == TransitionState.FINISHED) {
+ Trace.endAsyncSection(traceName, traceCookie)
+ }
+ }
+
companion object {
private const val TAG = "KeyguardTransitionRepository"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index f663b0d..914b9fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -21,15 +21,14 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
-import kotlin.reflect.KClass
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart
@@ -70,10 +69,10 @@
* @param expandable An optional [Expandable] for the activity- or dialog-launch animation
*/
fun onQuickAffordanceClicked(
- configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ configKey: String,
expandable: Expandable?,
) {
- @Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>)
+ @Suppress("UNCHECKED_CAST") val config = registry.get(configKey)
when (val result = config.onQuickAffordanceClicked(expandable)) {
is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
launchQuickAffordance(
@@ -102,7 +101,7 @@
if (index != -1) {
val visibleState = states[index] as KeyguardQuickAffordanceConfig.State.Visible
KeyguardQuickAffordanceModel.Visible(
- configKey = configs[index]::class,
+ configKey = configs[index].key,
icon = visibleState.icon,
toggle = visibleState.toggle,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 59bb22786..7409aec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -24,6 +24,8 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
/** Encapsulates business-logic related to the keyguard transitions. */
@SysUISingleton
@@ -34,4 +36,17 @@
) {
/** AOD->LOCKSCREEN transition information. */
val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
+
+ /** LOCKSCREEN->AOD transition information. */
+ val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
+
+ /**
+ * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
+ * Lockscreen (0f).
+ */
+ val dozeAmountTransition: Flow<TransitionStep> =
+ merge(
+ aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) },
+ lockscreenToAodTransition,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index e56b259..fc644a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -18,9 +18,7 @@
package com.android.systemui.keyguard.domain.model
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
-import kotlin.reflect.KClass
/**
* Models a "quick affordance" in the keyguard bottom area (for example, a button on the
@@ -33,7 +31,7 @@
/** A affordance is visible. */
data class Visible(
/** Identifier for the affordance this is modeling. */
- val configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ val configKey: String,
/** An icon for the affordance. */
val icon: Icon,
/** The toggle state for the affordance. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
index 94024d4..b48acb6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.quickaffordance
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import dagger.Binds
import dagger.Module
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
index ad40ee7..8526ada 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
@@ -17,14 +17,17 @@
package com.android.systemui.keyguard.domain.quickaffordance
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import javax.inject.Inject
-import kotlin.reflect.KClass
/** Central registry of all known quick affordance configs. */
interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
fun getAll(position: KeyguardQuickAffordancePosition): List<T>
- fun get(configClass: KClass<out T>): T
+ fun get(key: String): T
}
class KeyguardQuickAffordanceRegistryImpl
@@ -46,8 +49,8 @@
qrCodeScanner,
),
)
- private val configByClass =
- configsByPosition.values.flatten().associateBy { config -> config::class }
+ private val configByKey =
+ configsByPosition.values.flatten().associateBy { config -> config.key }
override fun getAll(
position: KeyguardQuickAffordancePosition,
@@ -56,8 +59,8 @@
}
override fun get(
- configClass: KClass<out KeyguardQuickAffordanceConfig>
+ key: String,
): KeyguardQuickAffordanceConfig {
- return configByClass.getValue(configClass)
+ return configByKey.getValue(key)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
index 581dafa3..a18b036 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package com.android.systemui.keyguard.shared.quickaffordance
/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
enum class KeyguardQuickAffordancePosition {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 535ca72..6aac912 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -22,7 +22,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index bf598ba..44f48f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -18,12 +18,10 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import kotlin.reflect.KClass
/** Models the UI state of a keyguard quick affordance button. */
data class KeyguardQuickAffordanceViewModel(
- val configKey: KClass<out KeyguardQuickAffordanceConfig>? = null,
+ val configKey: String? = null,
val isVisible: Boolean = false,
/** Whether to animate the transition of the quick affordance from invisible to visible. */
val animateReveal: Boolean = false,
@@ -33,7 +31,7 @@
val isActivated: Boolean = false,
) {
data class OnClickedParameters(
- val configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ val configKey: String,
val expandable: Expandable?,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index 38c971e..120f7d6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -43,6 +43,21 @@
)
}
+ /**
+ * Logs an error in trying to update to [displayState].
+ *
+ * [displayState] is either a [android.app.StatusBarManager.MediaTransferSenderState] or
+ * a [android.app.StatusBarManager.MediaTransferReceiverState].
+ */
+ fun logStateChangeError(displayState: Int) {
+ buffer.log(
+ tag,
+ LogLevel.ERROR,
+ { int1 = displayState },
+ { "Cannot display state=$int1; aborting" }
+ )
+ }
+
/** Logs that we couldn't find information for [packageName]. */
fun logPackageNotFound(packageName: String) {
buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 089625c..dc794e6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -25,7 +25,6 @@
import android.media.MediaRoute2Info
import android.os.Handler
import android.os.PowerManager
-import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
@@ -116,7 +115,7 @@
logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
if (chipState == null) {
- Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState")
+ logger.logStateChangeError(displayState)
return
}
uiEventLogger.logReceiverStateChange(chipState)
@@ -236,5 +235,3 @@
) : TemporaryViewInfo {
override fun getTimeoutMs() = DEFAULT_TIMEOUT_MILLIS
}
-
-private const val RECEIVER_TAG = "MediaTapToTransferRcvr"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index edf759d..1fa8fae 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -19,7 +19,6 @@
import android.app.StatusBarManager
import android.content.Context
import android.media.MediaRoute2Info
-import android.util.Log
import android.view.View
import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IUndoMediaTransferCallback
@@ -34,7 +33,6 @@
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
-import com.android.systemui.temporarydisplay.chipbar.SENDER_TAG
import javax.inject.Inject
/**
@@ -86,7 +84,7 @@
logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
if (chipState == null) {
- Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState")
+ logger.logStateChangeError(displayState)
return
}
uiEventLogger.logSenderStateChange(chipState)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index c089511..50cf63d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -25,11 +25,16 @@
import static android.app.StatusBarManager.windowStateToString;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
+import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
import static android.view.InsetsState.containsType;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
@@ -77,6 +82,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.Display;
+import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.InsetsFrameProvider;
@@ -240,6 +246,12 @@
private boolean mTransientShown;
private boolean mTransientShownFromGestureOnSystemBar;
+ /**
+ * This is to indicate whether the navigation bar button is forced visible. This is true
+ * when the setup wizard is on display. When that happens, the window frame should be provided
+ * as insets size directly.
+ */
+ private boolean mIsButtonForceVisible;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private LightBarController mLightBarController;
private final LightBarController mMainLightBarController;
@@ -623,6 +635,10 @@
mView.setTouchHandler(mTouchHandler);
setNavBarMode(mNavBarMode);
mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates);
+ mEdgeBackGestureHandler.setButtonForceVisibleChangeCallback((forceVisible) -> {
+ mIsButtonForceVisible = forceVisible;
+ repositionNavigationBar(mCurrentRotation);
+ });
mNavigationBarTransitions.addListener(this::onBarTransition);
mView.updateRotationButton();
@@ -810,7 +826,6 @@
mLayoutDirection = ld;
refreshLayout(ld);
}
-
repositionNavigationBar(rotation);
if (canShowSecondaryHandle()) {
if (rotation != mCurrentRotation) {
@@ -1599,23 +1614,15 @@
width,
height,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
- WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
lp.gravity = gravity;
- if (insetsHeight != -1) {
- lp.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, insetsHeight))
- };
- } else {
- lp.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_NAVIGATION_BAR)
- };
- }
+ lp.providedInsets = getInsetsFrameProvider(insetsHeight, userContext);
+
lp.token = new Binder();
lp.accessibilityTitle = userContext.getString(R.string.nav_bar);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC
@@ -1628,6 +1635,68 @@
return lp;
}
+ private InsetsFrameProvider[] getInsetsFrameProvider(int insetsHeight, Context userContext) {
+ final InsetsFrameProvider navBarProvider;
+ if (insetsHeight != -1 && !mIsButtonForceVisible) {
+ navBarProvider = new InsetsFrameProvider(
+ ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, insetsHeight));
+ // Use window frame for IME.
+ navBarProvider.insetsSizeOverrides = new InsetsFrameProvider.InsetsSizeOverride[] {
+ new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
+ };
+ } else {
+ navBarProvider = new InsetsFrameProvider(ITYPE_NAVIGATION_BAR);
+ navBarProvider.insetsSizeOverrides = new InsetsFrameProvider.InsetsSizeOverride[]{
+ new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
+ };
+ }
+ final boolean navBarTapThrough = userContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_navBarTapThrough);
+ final InsetsFrameProvider bottomTappableProvider;
+ if (navBarTapThrough) {
+ bottomTappableProvider = new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT,
+ Insets.of(0, 0, 0, 0));
+ } else {
+ bottomTappableProvider = new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT);
+ }
+
+ if (!mEdgeBackGestureHandler.isHandlingGestures()) {
+ // 2/3 button navigation is on. Do not provide any gesture insets here. But need to keep
+ // the provider to support runtime update.
+ return new InsetsFrameProvider[] {
+ navBarProvider,
+ new InsetsFrameProvider(
+ ITYPE_BOTTOM_MANDATORY_GESTURES, Insets.NONE),
+ new InsetsFrameProvider(ITYPE_LEFT_GESTURES, InsetsFrameProvider.SOURCE_DISPLAY,
+ Insets.NONE, null),
+ new InsetsFrameProvider(ITYPE_RIGHT_GESTURES,
+ InsetsFrameProvider.SOURCE_DISPLAY,
+ Insets.NONE, null),
+ bottomTappableProvider
+ };
+ } else {
+ // Gesture navigation
+ final int gestureHeight = userContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_gesture_height);
+ final DisplayCutout cutout = userContext.getDisplay().getCutout();
+ final int safeInsetsLeft = cutout != null ? cutout.getSafeInsetLeft() : 0;
+ final int safeInsetsRight = cutout != null ? cutout.getSafeInsetRight() : 0;
+ return new InsetsFrameProvider[] {
+ navBarProvider,
+ new InsetsFrameProvider(
+ ITYPE_BOTTOM_MANDATORY_GESTURES, Insets.of(0, 0, 0, gestureHeight)),
+ new InsetsFrameProvider(ITYPE_LEFT_GESTURES, InsetsFrameProvider.SOURCE_DISPLAY,
+ Insets.of(safeInsetsLeft
+ + mEdgeBackGestureHandler.getEdgeWidthLeft(), 0, 0, 0), null),
+ new InsetsFrameProvider(ITYPE_RIGHT_GESTURES,
+ InsetsFrameProvider.SOURCE_DISPLAY,
+ Insets.of(0, 0, safeInsetsRight
+ + mEdgeBackGestureHandler.getEdgeWidthRight(), 0), null),
+ bottomTappableProvider
+ };
+ }
+ }
+
private boolean canShowSecondaryHandle() {
return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 709467f..10ff48b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -175,6 +175,7 @@
private final OverviewProxyService mOverviewProxyService;
private final SysUiState mSysUiState;
private Runnable mStateChangeCallback;
+ private Consumer<Boolean> mButtonForceVisibleCallback;
private final PluginManager mPluginManager;
private final ProtoTracer mProtoTracer;
@@ -240,6 +241,7 @@
private boolean mIsBackGestureAllowed;
private boolean mGestureBlockingActivityRunning;
private boolean mIsNewBackAffordanceEnabled;
+ private boolean mIsButtonForceVisible;
private InputMonitor mInputMonitor;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
@@ -402,12 +404,29 @@
mStateChangeCallback = callback;
}
+ public void setButtonForceVisibleChangeCallback(Consumer<Boolean> callback) {
+ mButtonForceVisibleCallback = callback;
+ }
+
+ public int getEdgeWidthLeft() {
+ return mEdgeWidthLeft;
+ }
+
+ public int getEdgeWidthRight() {
+ return mEdgeWidthRight;
+ }
+
public void updateCurrentUserResources() {
Resources res = mNavigationModeController.getCurrentUserContext().getResources();
mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res);
- mIsBackGestureAllowed =
- !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
+ final boolean previousForceVisible = mIsButtonForceVisible;
+ mIsButtonForceVisible =
+ mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
+ if (previousForceVisible != mIsButtonForceVisible && mButtonForceVisibleCallback != null) {
+ mButtonForceVisibleCallback.accept(mIsButtonForceVisible);
+ }
+ mIsBackGestureAllowed = !mIsButtonForceVisible;
final DisplayMetrics dm = res.getDisplayMetrics();
final float defaultGestureHeight = res.getDimension(
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
new file mode 100644
index 0000000..d247f24
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.app.KeyguardManager
+import android.content.Context
+import android.os.UserManager
+import android.view.KeyEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.kotlin.getOrNull
+import com.android.wm.shell.floating.FloatingTasks
+import java.util.Optional
+import javax.inject.Inject
+
+/**
+ * Entry point for creating and managing note.
+ *
+ * The controller decides how a note is launched based in the device state: locked or unlocked.
+ *
+ * Currently, we only support a single task per time.
+ */
+@SysUISingleton
+internal class NoteTaskController
+@Inject
+constructor(
+ private val context: Context,
+ private val intentResolver: NoteTaskIntentResolver,
+ private val optionalFloatingTasks: Optional<FloatingTasks>,
+ private val optionalKeyguardManager: Optional<KeyguardManager>,
+ private val optionalUserManager: Optional<UserManager>,
+ @NoteTaskEnabledKey private val isEnabled: Boolean,
+) {
+
+ fun handleSystemKey(keyCode: Int) {
+ if (!isEnabled) return
+
+ if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
+ showNoteTask()
+ }
+ }
+
+ private fun showNoteTask() {
+ val floatingTasks = optionalFloatingTasks.getOrNull() ?: return
+ val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
+ val userManager = optionalUserManager.getOrNull() ?: return
+ val intent = intentResolver.resolveIntent() ?: return
+
+ // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
+ if (!userManager.isUserUnlocked) return
+
+ if (keyguardManager.isKeyguardLocked) {
+ context.startActivity(intent)
+ } else {
+ // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
+ floatingTasks.showOrSetStashed(intent)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
similarity index 72%
copy from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
copy to packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
index 581dafa3..e0bf1da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package com.android.systemui.notetask
-/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
-enum class KeyguardQuickAffordancePosition {
- BOTTOM_START,
- BOTTOM_END,
-}
+import javax.inject.Qualifier
+
+/** Key associated with a [Boolean] flag that enables or disables the note task feature. */
+@Qualifier internal annotation class NoteTaskEnabledKey
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
new file mode 100644
index 0000000..d84717d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import com.android.systemui.statusbar.CommandQueue
+import com.android.wm.shell.floating.FloatingTasks
+import dagger.Lazy
+import java.util.Optional
+import javax.inject.Inject
+
+/** Class responsible to "glue" all note task dependencies. */
+internal class NoteTaskInitializer
+@Inject
+constructor(
+ private val optionalFloatingTasks: Optional<FloatingTasks>,
+ private val lazyNoteTaskController: Lazy<NoteTaskController>,
+ private val commandQueue: CommandQueue,
+ @NoteTaskEnabledKey private val isEnabled: Boolean,
+) {
+
+ private val callbacks =
+ object : CommandQueue.Callbacks {
+ override fun handleSystemKey(keyCode: Int) {
+ lazyNoteTaskController.get().handleSystemKey(keyCode)
+ }
+ }
+
+ fun initialize() {
+ if (isEnabled && optionalFloatingTasks.isPresent) {
+ commandQueue.addCallback(callbacks)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
new file mode 100644
index 0000000..98d6991
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import javax.inject.Inject
+
+/**
+ * Class responsible to query all apps and find one that can handle the [NOTES_ACTION]. If found, an
+ * [Intent] ready for be launched will be returned. Otherwise, returns null.
+ *
+ * TODO(b/248274123): should be revisited once the notes role is implemented.
+ */
+internal class NoteTaskIntentResolver
+@Inject
+constructor(
+ private val packageManager: PackageManager,
+) {
+
+ fun resolveIntent(): Intent? {
+ val intent = Intent(NOTES_ACTION)
+ val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
+ val infoList = packageManager.queryIntentActivities(intent, flags)
+
+ for (info in infoList) {
+ val packageName = info.serviceInfo.applicationInfo.packageName ?: continue
+ val activityName = resolveActivityNameForNotesAction(packageName) ?: continue
+
+ return Intent(NOTES_ACTION)
+ .setComponent(ComponentName(packageName, activityName))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ return null
+ }
+
+ private fun resolveActivityNameForNotesAction(packageName: String): String? {
+ val intent = Intent(NOTES_ACTION).setPackage(packageName)
+ val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
+ val resolveInfo = packageManager.resolveActivity(intent, flags)
+
+ val activityInfo = resolveInfo?.activityInfo ?: return null
+ if (activityInfo.name.isNullOrBlank()) return null
+ if (!activityInfo.exported) return null
+ if (!activityInfo.enabled) return null
+ if (!activityInfo.showWhenLocked) return null
+ if (!activityInfo.turnScreenOn) return null
+
+ return activityInfo.name
+ }
+
+ companion object {
+ // TODO(b/254606432): Use Intent.ACTION_NOTES and Intent.ACTION_NOTES_LOCKED instead.
+ const val NOTES_ACTION = "android.intent.action.NOTES"
+ }
+}
+
+private val ActivityInfo.showWhenLocked: Boolean
+ get() = flags and ActivityInfo.FLAG_SHOW_WHEN_LOCKED != 0
+
+private val ActivityInfo.turnScreenOn: Boolean
+ get() = flags and ActivityInfo.FLAG_TURN_SCREEN_ON != 0
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
new file mode 100644
index 0000000..035396a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.app.KeyguardManager
+import android.content.Context
+import android.os.UserManager
+import androidx.core.content.getSystemService
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import dagger.Module
+import dagger.Provides
+import java.util.*
+
+/** Compose all dependencies required by Note Task feature. */
+@Module
+internal class NoteTaskModule {
+
+ @[Provides NoteTaskEnabledKey]
+ fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
+ return featureFlags.isEnabled(Flags.NOTE_TASKS)
+ }
+
+ @Provides
+ fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
+ return Optional.ofNullable(context.getSystemService())
+ }
+
+ @Provides
+ fun provideOptionalUserManager(context: Context): Optional<UserManager> {
+ return Optional.ofNullable(context.getSystemService())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index b6b657e..57a00c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -204,6 +204,15 @@
Trace.endSection();
}
+ @Override
+ public void onUserListItemClicked(@NonNull UserRecord record,
+ @Nullable UserSwitchDialogController.DialogShower dialogShower) {
+ if (dialogShower != null) {
+ mDialogShower.dismiss();
+ }
+ super.onUserListItemClicked(record, dialogShower);
+ }
+
public void linkToViewGroup(ViewGroup viewGroup) {
PseudoGridView.ViewGroupAdapterBridge.link(viewGroup, this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 24c4723..a895d72 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -39,6 +39,7 @@
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewStub;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
@@ -62,6 +63,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -72,6 +74,8 @@
import java.util.List;
import java.util.concurrent.Executor;
+import javax.inject.Inject;
+
/**
* Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks.
*/
@@ -86,6 +90,7 @@
private final Handler mHandler;
private final Executor mBackgroundExecutor;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
@VisibleForTesting
protected InternetAdapter mAdapter;
@@ -109,6 +114,7 @@
private LinearLayout mInternetDialogLayout;
private LinearLayout mConnectedWifListLayout;
private LinearLayout mMobileNetworkLayout;
+ private LinearLayout mSecondaryMobileNetworkLayout;
private LinearLayout mTurnWifiOnLayout;
private LinearLayout mEthernetLayout;
private TextView mWifiToggleTitleText;
@@ -123,6 +129,8 @@
private ImageView mSignalIcon;
private TextView mMobileTitleText;
private TextView mMobileSummaryText;
+ private TextView mSecondaryMobileTitleText;
+ private TextView mSecondaryMobileSummaryText;
private TextView mAirplaneModeSummaryText;
private Switch mMobileDataToggle;
private View mMobileToggleDivider;
@@ -158,9 +166,11 @@
mInternetDialogSubTitle.setText(getSubtitleText());
};
+ @Inject
public InternetDialog(Context context, InternetDialogFactory internetDialogFactory,
InternetDialogController internetDialogController, boolean canConfigMobileData,
boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger,
+ DialogLaunchAnimator dialogLaunchAnimator,
@Main Handler handler, @Background Executor executor,
KeyguardStateController keyguardStateController) {
super(context);
@@ -183,6 +193,7 @@
mKeyguard = keyguardStateController;
mUiEventLogger = uiEventLogger;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
mAdapter = new InternetAdapter(mInternetDialogController);
if (!aboveStatusBar) {
getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
@@ -287,6 +298,9 @@
mMobileNetworkLayout.setOnClickListener(null);
mMobileDataToggle.setOnCheckedChangeListener(null);
mConnectedWifListLayout.setOnClickListener(null);
+ if (mSecondaryMobileNetworkLayout != null) {
+ mSecondaryMobileNetworkLayout.setOnClickListener(null);
+ }
mSeeAllLayout.setOnClickListener(null);
mWiFiToggle.setOnCheckedChangeListener(null);
mDoneButton.setOnClickListener(null);
@@ -341,6 +355,10 @@
private void setOnClickListener() {
mMobileNetworkLayout.setOnClickListener(v -> {
+ int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+ if (autoSwitchNonDdsSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ showTurnOffAutoDataSwitchDialog(autoSwitchNonDdsSubId);
+ }
mInternetDialogController.connectCarrierNetwork();
});
mMobileDataToggle.setOnCheckedChangeListener(
@@ -385,11 +403,14 @@
if (!mInternetDialogController.hasActiveSubId()
&& (!isWifiEnabled || !isCarrierNetworkActive)) {
mMobileNetworkLayout.setVisibility(View.GONE);
+ if (mSecondaryMobileNetworkLayout != null) {
+ mSecondaryMobileNetworkLayout.setVisibility(View.GONE);
+ }
} else {
mMobileNetworkLayout.setVisibility(View.VISIBLE);
mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled());
- mMobileTitleText.setText(getMobileNetworkTitle());
- String summary = getMobileNetworkSummary();
+ mMobileTitleText.setText(getMobileNetworkTitle(mDefaultDataSubId));
+ String summary = getMobileNetworkSummary(mDefaultDataSubId);
if (!TextUtils.isEmpty(summary)) {
mMobileSummaryText.setText(
Html.fromHtml(summary, Html.FROM_HTML_MODE_LEGACY));
@@ -399,28 +420,11 @@
mMobileSummaryText.setVisibility(View.GONE);
}
mBackgroundExecutor.execute(() -> {
- Drawable drawable = getSignalStrengthDrawable();
+ Drawable drawable = getSignalStrengthDrawable(mDefaultDataSubId);
mHandler.post(() -> {
mSignalIcon.setImageDrawable(drawable);
});
});
- mMobileTitleText.setTextAppearance(isNetworkConnected
- ? R.style.TextAppearance_InternetDialog_Active
- : R.style.TextAppearance_InternetDialog);
- int secondaryRes = isNetworkConnected
- ? R.style.TextAppearance_InternetDialog_Secondary_Active
- : R.style.TextAppearance_InternetDialog_Secondary;
- mMobileSummaryText.setTextAppearance(secondaryRes);
- // Set airplane mode to the summary for carrier network
- if (mInternetDialogController.isAirplaneModeEnabled()) {
- mAirplaneModeSummaryText.setVisibility(View.VISIBLE);
- mAirplaneModeSummaryText.setText(mContext.getText(R.string.airplane_mode));
- mAirplaneModeSummaryText.setTextAppearance(secondaryRes);
- } else {
- mAirplaneModeSummaryText.setVisibility(View.GONE);
- }
- mMobileNetworkLayout.setBackground(
- isNetworkConnected ? mBackgroundOn : mBackgroundOff);
TypedArray array = mContext.obtainStyledAttributes(
R.style.InternetDialog_Divider_Active, new int[]{android.R.attr.background});
@@ -433,6 +437,86 @@
mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
mMobileToggleDivider.setVisibility(
mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
+
+ // Display the info for the non-DDS if it's actively being used
+ int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+ int nonDdsVisibility = autoSwitchNonDdsSubId
+ != SubscriptionManager.INVALID_SUBSCRIPTION_ID ? View.VISIBLE : View.GONE;
+
+ int secondaryRes = isNetworkConnected
+ ? R.style.TextAppearance_InternetDialog_Secondary_Active
+ : R.style.TextAppearance_InternetDialog_Secondary;
+ if (nonDdsVisibility == View.VISIBLE) {
+ // non DDS is the currently active sub, set primary visual for it
+ ViewStub stub = mDialogView.findViewById(R.id.secondary_mobile_network_stub);
+ if (stub != null) {
+ stub.inflate();
+ }
+ mSecondaryMobileNetworkLayout = findViewById(R.id.secondary_mobile_network_layout);
+ mSecondaryMobileNetworkLayout.setOnClickListener(
+ this::onClickConnectedSecondarySub);
+ mSecondaryMobileNetworkLayout.setBackground(mBackgroundOn);
+
+ mSecondaryMobileTitleText = mDialogView.requireViewById(
+ R.id.secondary_mobile_title);
+ mSecondaryMobileTitleText.setText(getMobileNetworkTitle(autoSwitchNonDdsSubId));
+ mSecondaryMobileTitleText.setTextAppearance(
+ R.style.TextAppearance_InternetDialog_Active);
+
+ mSecondaryMobileSummaryText =
+ mDialogView.requireViewById(R.id.secondary_mobile_summary);
+ summary = getMobileNetworkSummary(autoSwitchNonDdsSubId);
+ if (!TextUtils.isEmpty(summary)) {
+ mSecondaryMobileSummaryText.setText(
+ Html.fromHtml(summary, Html.FROM_HTML_MODE_LEGACY));
+ mSecondaryMobileSummaryText.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE);
+ mSecondaryMobileSummaryText.setTextAppearance(
+ R.style.TextAppearance_InternetDialog_Active);
+ }
+
+ ImageView mSecondarySignalIcon =
+ mDialogView.requireViewById(R.id.secondary_signal_icon);
+ mBackgroundExecutor.execute(() -> {
+ Drawable drawable = getSignalStrengthDrawable(autoSwitchNonDdsSubId);
+ mHandler.post(() -> {
+ mSecondarySignalIcon.setImageDrawable(drawable);
+ });
+ });
+
+ ImageView mSecondaryMobileSettingsIcon =
+ mDialogView.requireViewById(R.id.secondary_settings_icon);
+ mSecondaryMobileSettingsIcon.setColorFilter(
+ mContext.getColor(R.color.connected_network_primary_color));
+
+ // set secondary visual for default data sub
+ mMobileNetworkLayout.setBackground(mBackgroundOff);
+ mMobileTitleText.setTextAppearance(R.style.TextAppearance_InternetDialog);
+ mMobileSummaryText.setTextAppearance(
+ R.style.TextAppearance_InternetDialog_Secondary);
+ mSignalIcon.setColorFilter(
+ mContext.getColor(R.color.connected_network_secondary_color));
+ } else {
+ mMobileNetworkLayout.setBackground(
+ isNetworkConnected ? mBackgroundOn : mBackgroundOff);
+ mMobileTitleText.setTextAppearance(isNetworkConnected
+ ?
+ R.style.TextAppearance_InternetDialog_Active
+ : R.style.TextAppearance_InternetDialog);
+ mMobileSummaryText.setTextAppearance(secondaryRes);
+ }
+
+ if (mSecondaryMobileNetworkLayout != null) {
+ mSecondaryMobileNetworkLayout.setVisibility(nonDdsVisibility);
+ }
+
+ // Set airplane mode to the summary for carrier network
+ if (mInternetDialogController.isAirplaneModeEnabled()) {
+ mAirplaneModeSummaryText.setVisibility(View.VISIBLE);
+ mAirplaneModeSummaryText.setText(mContext.getText(R.string.airplane_mode));
+ mAirplaneModeSummaryText.setTextAppearance(secondaryRes);
+ } else {
+ mAirplaneModeSummaryText.setVisibility(View.GONE);
+ }
}
}
@@ -471,6 +555,10 @@
mInternetDialogController.getInternetWifiDrawable(mConnectedWifiEntry));
mWifiSettingsIcon.setColorFilter(
mContext.getColor(R.color.connected_network_primary_color));
+
+ if (mSecondaryMobileNetworkLayout != null) {
+ mSecondaryMobileNetworkLayout.setVisibility(View.GONE);
+ }
}
@MainThread
@@ -541,6 +629,11 @@
mInternetDialogController.launchWifiDetailsSetting(mConnectedWifiEntry.getKey(), view);
}
+ /** For DSDS auto data switch **/
+ void onClickConnectedSecondarySub(View view) {
+ mInternetDialogController.launchMobileNetworkSettings(view);
+ }
+
void onClickSeeMoreButton(View view) {
mInternetDialogController.launchNetworkSetting(view);
}
@@ -555,16 +648,16 @@
mIsProgressBarVisible && !mIsSearchingHidden);
}
- private Drawable getSignalStrengthDrawable() {
- return mInternetDialogController.getSignalStrengthDrawable();
+ private Drawable getSignalStrengthDrawable(int subId) {
+ return mInternetDialogController.getSignalStrengthDrawable(subId);
}
- CharSequence getMobileNetworkTitle() {
- return mInternetDialogController.getMobileNetworkTitle();
+ CharSequence getMobileNetworkTitle(int subId) {
+ return mInternetDialogController.getMobileNetworkTitle(subId);
}
- String getMobileNetworkSummary() {
- return mInternetDialogController.getMobileNetworkSummary();
+ String getMobileNetworkSummary(int subId) {
+ return mInternetDialogController.getMobileNetworkSummary(subId);
}
protected void showProgressBar() {
@@ -602,8 +695,8 @@
}
private void showTurnOffMobileDialog() {
- CharSequence carrierName = getMobileNetworkTitle();
- boolean isInService = mInternetDialogController.isVoiceStateInService();
+ CharSequence carrierName = getMobileNetworkTitle(mDefaultDataSubId);
+ boolean isInService = mInternetDialogController.isVoiceStateInService(mDefaultDataSubId);
if (TextUtils.isEmpty(carrierName) || !isInService) {
carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier);
}
@@ -627,7 +720,33 @@
SystemUIDialog.setShowForAllUsers(mAlertDialog, true);
SystemUIDialog.registerDismissListener(mAlertDialog);
SystemUIDialog.setWindowOnTop(mAlertDialog, mKeyguard.isShowing());
- mAlertDialog.show();
+ mDialogLaunchAnimator.showFromDialog(mAlertDialog, this, null, false);
+ }
+
+ private void showTurnOffAutoDataSwitchDialog(int subId) {
+ CharSequence carrierName = getMobileNetworkTitle(mDefaultDataSubId);
+ if (TextUtils.isEmpty(carrierName)) {
+ carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier);
+ }
+ mAlertDialog = new Builder(mContext)
+ .setTitle(mContext.getString(R.string.auto_data_switch_disable_title, carrierName))
+ .setMessage(R.string.auto_data_switch_disable_message)
+ .setNegativeButton(R.string.auto_data_switch_dialog_negative_button,
+ (d, w) -> {})
+ .setPositiveButton(R.string.auto_data_switch_dialog_positive_button,
+ (d, w) -> {
+ mInternetDialogController
+ .setAutoDataSwitchMobileDataPolicy(subId, false);
+ if (mSecondaryMobileNetworkLayout != null) {
+ mSecondaryMobileNetworkLayout.setVisibility(View.GONE);
+ }
+ })
+ .create();
+ mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ SystemUIDialog.setShowForAllUsers(mAlertDialog, true);
+ SystemUIDialog.registerDismissListener(mAlertDialog);
+ SystemUIDialog.setWindowOnTop(mAlertDialog, mKeyguard.isShowing());
+ mDialogLaunchAnimator.showFromDialog(mAlertDialog, this, null, false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 0e00c46..aa6e678 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -37,6 +37,7 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.wifi.WifiManager;
+import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
@@ -78,6 +79,8 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.connectivity.AccessPointController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -90,6 +93,7 @@
import com.android.wifitrackerlib.WifiEntry;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -113,6 +117,17 @@
"android.settings.NETWORK_PROVIDER_SETTINGS";
private static final String ACTION_WIFI_SCANNING_SETTINGS =
"android.settings.WIFI_SCANNING_SETTINGS";
+ /**
+ * Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS}
+ */
+ private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+ /**
+ * When starting this activity, this extra can also be specified to supply a Bundle of arguments
+ * to pass to that fragment when it is instantiated during the initial creation of the activity.
+ */
+ private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS =
+ ":settings:show_fragment_args";
+ private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch";
public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
public static final int NO_CELL_DATA_TYPE_ICON = 0;
private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off;
@@ -130,9 +145,12 @@
static final int MAX_WIFI_ENTRY_COUNT = 3;
+ private final FeatureFlags mFeatureFlags;
+
private WifiManager mWifiManager;
private Context mContext;
private SubscriptionManager mSubscriptionManager;
+ private Map<Integer, TelephonyManager> mSubIdTelephonyManagerMap = new HashMap<>();
private TelephonyManager mTelephonyManager;
private ConnectivityManager mConnectivityManager;
private CarrierConfigTracker mCarrierConfigTracker;
@@ -155,6 +173,7 @@
private WindowManager mWindowManager;
private ToastFactory mToastFactory;
private SignalDrawable mSignalDrawable;
+ private SignalDrawable mSecondarySignalDrawable; // For the secondary mobile data sub in DSDS
private LocationController mLocationController;
private DialogLaunchAnimator mDialogLaunchAnimator;
private boolean mHasWifiEntries;
@@ -213,7 +232,8 @@
CarrierConfigTracker carrierConfigTracker,
LocationController locationController,
DialogLaunchAnimator dialogLaunchAnimator,
- WifiStateWorker wifiStateWorker
+ WifiStateWorker wifiStateWorker,
+ FeatureFlags featureFlags
) {
if (DEBUG) {
Log.d(TAG, "Init InternetDialogController");
@@ -242,10 +262,12 @@
mWindowManager = windowManager;
mToastFactory = toastFactory;
mSignalDrawable = new SignalDrawable(mContext);
+ mSecondarySignalDrawable = new SignalDrawable(mContext);
mLocationController = locationController;
mDialogLaunchAnimator = dialogLaunchAnimator;
mConnectedWifiInternetMonitor = new ConnectedWifiInternetMonitor();
mWifiStateWorker = wifiStateWorker;
+ mFeatureFlags = featureFlags;
}
void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
@@ -267,6 +289,7 @@
}
mConfig = MobileMappings.Config.readConfig(mContext);
mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mSubIdTelephonyManagerMap.put(mDefaultDataSubId, mTelephonyManager);
mInternetTelephonyCallback = new InternetTelephonyCallback();
mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
// Listen the connectivity changes
@@ -280,7 +303,9 @@
Log.d(TAG, "onStop");
}
mBroadcastDispatcher.unregisterReceiver(mConnectionStateReceiver);
- mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
+ for (TelephonyManager tm : mSubIdTelephonyManagerMap.values()) {
+ tm.unregisterTelephonyCallback(mInternetTelephonyCallback);
+ }
mSubscriptionManager.removeOnSubscriptionsChangedListener(
mOnSubscriptionsChangedListener);
mAccessPointController.removeAccessPointCallback(this);
@@ -371,7 +396,10 @@
if (DEBUG) {
Log.d(TAG, "No Wi-Fi item.");
}
- if (!hasActiveSubId() || (!isVoiceStateInService() && !isDataStateInService())) {
+ boolean isActiveOnNonDds = getActiveAutoSwitchNonDdsSubId() != SubscriptionManager
+ .INVALID_SUBSCRIPTION_ID;
+ if (!hasActiveSubId() || (!isVoiceStateInService(mDefaultDataSubId)
+ && !isDataStateInService(mDefaultDataSubId) && !isActiveOnNonDds)) {
if (DEBUG) {
Log.d(TAG, "No carrier or service is out of service.");
}
@@ -412,7 +440,7 @@
return drawable;
}
- Drawable getSignalStrengthDrawable() {
+ Drawable getSignalStrengthDrawable(int subId) {
Drawable drawable = mContext.getDrawable(
R.drawable.ic_signal_strength_zero_bar_no_internet);
try {
@@ -424,9 +452,10 @@
}
boolean isCarrierNetworkActive = isCarrierNetworkActive();
- if (isDataStateInService() || isVoiceStateInService() || isCarrierNetworkActive) {
+ if (isDataStateInService(subId) || isVoiceStateInService(subId)
+ || isCarrierNetworkActive) {
AtomicReference<Drawable> shared = new AtomicReference<>();
- shared.set(getSignalStrengthDrawableWithLevel(isCarrierNetworkActive));
+ shared.set(getSignalStrengthDrawableWithLevel(isCarrierNetworkActive, subId));
drawable = shared.get();
}
@@ -447,24 +476,30 @@
*
* @return The Drawable which is a signal bar icon with level.
*/
- Drawable getSignalStrengthDrawableWithLevel(boolean isCarrierNetworkActive) {
- final SignalStrength strength = mTelephonyManager.getSignalStrength();
+ Drawable getSignalStrengthDrawableWithLevel(boolean isCarrierNetworkActive, int subId) {
+ TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager);
+ final SignalStrength strength = tm.getSignalStrength();
int level = (strength == null) ? 0 : strength.getLevel();
int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
if (isCarrierNetworkActive) {
level = getCarrierNetworkLevel();
numLevels = WifiEntry.WIFI_LEVEL_MAX + 1;
- } else if (mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) {
+ } else if (mSubscriptionManager != null && shouldInflateSignalStrength(subId)) {
level += 1;
numLevels += 1;
}
- return getSignalStrengthIcon(mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON,
+ return getSignalStrengthIcon(subId, mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON,
!isMobileDataEnabled());
}
- Drawable getSignalStrengthIcon(Context context, int level, int numLevels,
+ Drawable getSignalStrengthIcon(int subId, Context context, int level, int numLevels,
int iconType, boolean cutOut) {
- mSignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut));
+ boolean isForDds = subId == mDefaultDataSubId;
+ if (isForDds) {
+ mSignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut));
+ } else {
+ mSecondarySignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut));
+ }
// Make the network type drawable
final Drawable networkDrawable =
@@ -473,7 +508,8 @@
: context.getResources().getDrawable(iconType, context.getTheme());
// Overlay the two drawables
- final Drawable[] layers = {networkDrawable, mSignalDrawable};
+ final Drawable[] layers = {networkDrawable, isForDds
+ ? mSignalDrawable : mSecondarySignalDrawable};
final int iconSize =
context.getResources().getDimensionPixelSize(R.dimen.signal_strength_icon_size);
@@ -571,14 +607,39 @@
info -> info.uniqueName));
}
- CharSequence getMobileNetworkTitle() {
- return getUniqueSubscriptionDisplayName(mDefaultDataSubId, mContext);
+ /**
+ * @return the subId of the visible non-DDS if it's actively being used for data, otherwise
+ * return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+ */
+ int getActiveAutoSwitchNonDdsSubId() {
+ if (!mFeatureFlags.isEnabled(Flags.QS_SECONDARY_DATA_SUB_INFO)) {
+ // sets the non-DDS to be not found to hide its visual
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+ SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(
+ SubscriptionManager.getActiveDataSubscriptionId());
+ if (subInfo != null && subInfo.getSubscriptionId() != mDefaultDataSubId
+ && !subInfo.isOpportunistic()) {
+ int subId = subInfo.getSubscriptionId();
+ if (mSubIdTelephonyManagerMap.get(subId) == null) {
+ TelephonyManager secondaryTm = mTelephonyManager.createForSubscriptionId(subId);
+ secondaryTm.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
+ mSubIdTelephonyManagerMap.put(subId, secondaryTm);
+ }
+ return subId;
+ }
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
}
- String getMobileNetworkSummary() {
+ CharSequence getMobileNetworkTitle(int subId) {
+ return getUniqueSubscriptionDisplayName(subId, mContext);
+ }
+
+ String getMobileNetworkSummary(int subId) {
String description = getNetworkTypeDescription(mContext, mConfig,
- mTelephonyDisplayInfo, mDefaultDataSubId);
- return getMobileSummary(mContext, description);
+ mTelephonyDisplayInfo, subId);
+ return getMobileSummary(mContext, description, subId);
}
/**
@@ -606,22 +667,28 @@
? SubscriptionManager.getResourcesForSubId(context, subId).getString(resId) : "";
}
- private String getMobileSummary(Context context, String networkTypeDescription) {
+ private String getMobileSummary(Context context, String networkTypeDescription, int subId) {
if (!isMobileDataEnabled()) {
return context.getString(R.string.mobile_data_off_summary);
}
String summary = networkTypeDescription;
+ boolean isForDds = subId == mDefaultDataSubId;
+ int activeSubId = getActiveAutoSwitchNonDdsSubId();
+ boolean isOnNonDds = activeSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID;
// Set network description for the carrier network when connecting to the carrier network
// under the airplane mode ON.
if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
summary = context.getString(R.string.preference_summary_default_combination,
- context.getString(R.string.mobile_data_connection_active),
+ context.getString(
+ isForDds // if nonDds is active, explains Dds status as poor connection
+ ? (isOnNonDds ? R.string.mobile_data_poor_connection
+ : R.string.mobile_data_connection_active)
+ : R.string.mobile_data_temp_connection_active),
networkTypeDescription);
- } else if (!isDataStateInService()) {
+ } else if (!isDataStateInService(subId)) {
summary = context.getString(R.string.mobile_data_no_connection);
}
-
return summary;
}
@@ -647,6 +714,26 @@
}
}
+ void launchMobileNetworkSettings(View view) {
+ final int subId = getActiveAutoSwitchNonDdsSubId();
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ Log.w(TAG, "launchMobileNetworkSettings fail, invalid subId:" + subId);
+ return;
+ }
+ startActivity(getSubSettingIntent(subId), view);
+ }
+
+ Intent getSubSettingIntent(int subId) {
+ final Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
+
+ final Bundle fragmentArgs = new Bundle();
+ // Special contract for Settings to highlight permission row
+ fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID);
+ fragmentArgs.putInt(Settings.EXTRA_SUB_ID, subId);
+ intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
+ return intent;
+ }
+
void launchWifiScanningSetting(View view) {
final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -824,8 +911,20 @@
mWorkerHandler.post(() -> setMergedCarrierWifiEnabledIfNeed(subId, enabled));
}
- boolean isDataStateInService() {
- final ServiceState serviceState = mTelephonyManager.getServiceState();
+ void setAutoDataSwitchMobileDataPolicy(int subId, boolean enable) {
+ TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager);
+ if (tm == null) {
+ if (DEBUG) {
+ Log.d(TAG, "TelephonyManager is null, can not set mobile data.");
+ }
+ return;
+ }
+ tm.setMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, enable);
+ }
+
+ boolean isDataStateInService(int subId) {
+ TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager);
+ final ServiceState serviceState = tm.getServiceState();
NetworkRegistrationInfo regInfo =
(serviceState == null) ? null : serviceState.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS,
@@ -833,7 +932,7 @@
return (regInfo == null) ? false : regInfo.isRegistered();
}
- boolean isVoiceStateInService() {
+ boolean isVoiceStateInService(int subId) {
if (mTelephonyManager == null) {
if (DEBUG) {
Log.d(TAG, "TelephonyManager is null, can not detect voice state.");
@@ -841,7 +940,8 @@
return false;
}
- final ServiceState serviceState = mTelephonyManager.getServiceState();
+ TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager);
+ final ServiceState serviceState = tm.getServiceState();
return serviceState != null
&& serviceState.getState() == serviceState.STATE_IN_SERVICE;
}
@@ -1132,6 +1232,7 @@
if (SubscriptionManager.isUsableSubscriptionId(mDefaultDataSubId)) {
mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mSubIdTelephonyManagerMap.put(mDefaultDataSubId, mTelephonyManager);
mTelephonyManager.registerTelephonyCallback(mHandler::post,
mInternetTelephonyCallback);
mCallback.onSubscriptionsChanged(mDefaultDataSubId);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
index 8566ca3..796672d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -66,7 +66,8 @@
} else {
internetDialog = InternetDialog(
context, this, internetDialogController,
- canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
+ canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger,
+ dialogLaunchAnimator, handler,
executor, keyguardStateController
)
if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 231e415..d524a35 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -20,6 +20,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
@@ -634,6 +635,11 @@
return true;
}
});
+
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+ mScreenshotView.badgeScreenshot(
+ mContext.getPackageManager().getUserBadgeForDensity(owner, 0));
+ }
mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
if (DEBUG_WINDOW) {
Log.d(TAG, "setContentView: " + mScreenshotView);
@@ -1038,7 +1044,7 @@
private boolean isUserSetupComplete(UserHandle owner) {
return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
- .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 1b9cdd4..27331ae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -74,7 +74,6 @@
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -122,15 +121,9 @@
private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
- private static final long SCREENSHOT_DISMISS_X_DURATION_MS = 350;
- private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 350;
- private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
- private static final float ROUNDED_CORNER_RADIUS = .25f;
private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
- private final Interpolator mAccelerateInterpolator = new AccelerateInterpolator();
-
private final Resources mResources;
private final Interpolator mFastOutSlowIn;
private final DisplayMetrics mDisplayMetrics;
@@ -145,6 +138,7 @@
private ImageView mScrollingScrim;
private DraggableConstraintLayout mScreenshotStatic;
private ImageView mScreenshotPreview;
+ private ImageView mScreenshotBadge;
private View mScreenshotPreviewBorder;
private ImageView mScrollablePreview;
private ImageView mScreenshotFlash;
@@ -355,6 +349,7 @@
mScreenshotPreviewBorder = requireNonNull(
findViewById(R.id.screenshot_preview_border));
mScreenshotPreview.setClipToOutline(true);
+ mScreenshotBadge = requireNonNull(findViewById(R.id.screenshot_badge));
mActionsContainerBackground = requireNonNull(findViewById(
R.id.actions_container_background));
@@ -595,8 +590,11 @@
ValueAnimator borderFadeIn = ValueAnimator.ofFloat(0, 1);
borderFadeIn.setDuration(100);
- borderFadeIn.addUpdateListener((animation) ->
- mScreenshotPreviewBorder.setAlpha(animation.getAnimatedFraction()));
+ borderFadeIn.addUpdateListener((animation) -> {
+ float borderAlpha = animation.getAnimatedFraction();
+ mScreenshotPreviewBorder.setAlpha(borderAlpha);
+ mScreenshotBadge.setAlpha(borderAlpha);
+ });
if (showFlash) {
dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
@@ -763,6 +761,11 @@
return animator;
}
+ void badgeScreenshot(Drawable badge) {
+ mScreenshotBadge.setImageDrawable(badge);
+ mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
+ }
+
void setChipIntents(ScreenshotController.SavedImageData imageData) {
mShareChip.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
@@ -1027,6 +1030,9 @@
mScreenshotPreview.setVisibility(View.INVISIBLE);
mScreenshotPreview.setAlpha(1f);
mScreenshotPreviewBorder.setAlpha(0);
+ mScreenshotBadge.setAlpha(0f);
+ mScreenshotBadge.setVisibility(View.GONE);
+ mScreenshotBadge.setImageDrawable(null);
mPendingSharedTransition = false;
mActionsContainerBackground.setVisibility(View.GONE);
mActionsContainer.setVisibility(View.GONE);
@@ -1082,6 +1088,7 @@
mActionsContainerBackground.setAlpha(alpha);
mActionsContainer.setAlpha(alpha);
mScreenshotPreviewBorder.setAlpha(alpha);
+ mScreenshotBadge.setAlpha(alpha);
});
alphaAnim.setDuration(600);
return alphaAnim;
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index bbba007..b36f0d7 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -33,13 +33,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
/**
* Drawable used on SysUI scrims.
*/
public class ScrimDrawable extends Drawable {
private static final String TAG = "ScrimDrawable";
- private static final long COLOR_ANIMATION_DURATION = 2000;
private final Paint mPaint;
private int mAlpha = 255;
@@ -76,7 +76,7 @@
final int mainFrom = mMainColor;
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- anim.setDuration(COLOR_ANIMATION_DURATION);
+ anim.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
anim.addUpdateListener(animation -> {
float ratio = (float) animation.getAnimatedValue();
mMainColor = ColorUtils.blendARGB(mainFrom, mainColor, ratio);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 8de0365..277ad8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1374,13 +1374,8 @@
if (bubbleButton == null || actionContainer == null) {
return;
}
- boolean isPersonWithShortcut =
- mPeopleIdentifier.getPeopleNotificationType(entry)
- >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
- boolean showButton = BubblesManager.areBubblesEnabled(mContext, entry.getSbn().getUser())
- && isPersonWithShortcut
- && entry.getBubbleMetadata() != null;
- if (showButton) {
+
+ if (shouldShowBubbleButton(entry)) {
// explicitly resolve drawable resource using SystemUI's theme
Drawable d = mContext.getDrawable(entry.isBubble()
? R.drawable.bubble_ic_stop_bubble
@@ -1410,6 +1405,16 @@
}
}
+ @VisibleForTesting
+ boolean shouldShowBubbleButton(NotificationEntry entry) {
+ boolean isPersonWithShortcut =
+ mPeopleIdentifier.getPeopleNotificationType(entry)
+ >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
+ return BubblesManager.areBubblesEnabled(mContext, entry.getSbn().getUser())
+ && isPersonWithShortcut
+ && entry.getBubbleMetadata() != null;
+ }
+
private void applySnoozeAction(View layout) {
if (layout == null || mContainingNotification == null) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 29642be..43bc99f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2960,7 +2960,10 @@
// * When phone is unlocked: we still don't want to execute hiding of the keyguard
// as the animation could prepare 'fake AOD' interface (without actually
// transitioning to keyguard state) and this might reset the view states
- if (!mScreenOffAnimationController.isKeyguardHideDelayed()) {
+ if (!mScreenOffAnimationController.isKeyguardHideDelayed()
+ // If we're animating occluded, there's an activity launching over the keyguard
+ // UI. Wait to hide it until after the animation concludes.
+ && !mKeyguardViewMediator.isOccludeAnimationPlaying()) {
return hideKeyguardImpl(forceStateChange);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 9767103..c189ace 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -649,37 +649,6 @@
return mNumDots > 0;
}
- /**
- * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then
- * extra padding will have to be accounted for
- *
- * This method has no meaning for non-static containers
- */
- public boolean hasPartialOverflow() {
- return mNumDots > 0 && mNumDots < MAX_DOTS;
- }
-
- /**
- * Get padding that can account for extra dots up to the max. The only valid values for
- * this method are for 1 or 2 dots.
- * @return only extraDotPadding or extraDotPadding * 2
- */
- public int getPartialOverflowExtraPadding() {
- if (!hasPartialOverflow()) {
- return 0;
- }
-
- int partialOverflowAmount = (MAX_DOTS - mNumDots) * (mStaticDotDiameter + mDotPadding);
-
- int adjustedWidth = getFinalTranslationX() + partialOverflowAmount;
- // In case we actually give too much padding...
- if (adjustedWidth > getWidth()) {
- partialOverflowAmount = getWidth() - getFinalTranslationX();
- }
-
- return partialOverflowAmount;
- }
-
// Give some extra room for btw notifications if we can
public int getNoOverflowExtraPadding() {
if (mNumDots != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 8490768..cf3a48c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -53,6 +53,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -204,6 +205,7 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private KeyguardViewMediator mKeyguardViewMediator;
private GradientColors mColors;
private boolean mNeedsDrawableColorUpdate;
@@ -273,7 +275,8 @@
@Main Executor mainExecutor,
ScreenOffAnimationController screenOffAnimationController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ KeyguardViewMediator keyguardViewMediator) {
mScrimStateListener = lightBarController::setScrimState;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
@@ -312,6 +315,8 @@
}
});
mColors = new GradientColors();
+
+ mKeyguardViewMediator = keyguardViewMediator;
}
/**
@@ -807,6 +812,13 @@
mBehindTint,
interpolatedFraction);
}
+
+ // If we're unlocked but still playing the occlude animation, remain at the keyguard
+ // alpha temporarily.
+ if (mKeyguardViewMediator.isOccludeAnimationPlaying()
+ || mState.mLaunchingAffordanceWithPreview) {
+ mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
+ }
} else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
index a0415f2..6cd8c78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
@@ -22,8 +22,6 @@
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
@@ -42,7 +40,6 @@
@Inject
internal constructor(
private val centralSurfaces: CentralSurfaces,
- private val featureFlags: FeatureFlags,
private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
private val statusBarStateController: SysuiStatusBarStateController,
private val lightBarController: LightBarController,
@@ -127,15 +124,11 @@
}
private fun shouldUseLetterboxAppearance(letterboxDetails: Array<LetterboxDetails>) =
- isLetterboxAppearanceFlagEnabled() && letterboxDetails.isNotEmpty()
-
- private fun isLetterboxAppearanceFlagEnabled() =
- featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)
+ letterboxDetails.isNotEmpty()
private fun dump(printWriter: PrintWriter, strings: Array<String>) {
printWriter.println("lastSystemBarAttributesParams: $lastSystemBarAttributesParams")
printWriter.println("lastLetterboxAppearance: $lastLetterboxAppearance")
- printWriter.println("letterbox appearance flag: ${isLetterboxAppearanceFlagEnabled()}")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 2aaa085..fcd1b8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -18,10 +18,14 @@
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
@@ -41,10 +45,16 @@
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
@Binds
- abstract fun mobileSubscriptionRepository(
- impl: MobileSubscriptionRepositoryImpl
- ): MobileSubscriptionRepository
+ abstract fun mobileConnectionsRepository(
+ impl: MobileConnectionsRepositoryImpl
+ ): MobileConnectionsRepository
@Binds
abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
+
+ @Binds
+ abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
+
+ @Binds
+ abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
index 46ccf32c..eaba0e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
@@ -27,6 +27,7 @@
import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
/**
* Data class containing all of the relevant information for a particular line of service, known as
@@ -57,6 +58,11 @@
/** From [CarrierNetworkListener.onCarrierNetworkChange] */
val carrierNetworkChangeActive: Boolean? = null,
- /** From [DisplayInfoListener.onDisplayInfoChanged] */
- val displayInfo: TelephonyDisplayInfo? = null
+ /**
+ * From [DisplayInfoListener.onDisplayInfoChanged].
+ *
+ * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
+ * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
+ */
+ val resolvedNetworkType: ResolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
new file mode 100644
index 0000000..f385806
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.model
+
+import android.telephony.Annotation.NetworkType
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+
+/**
+ * A SysUI type to represent the [NetworkType] that we pull out of [TelephonyDisplayInfo]. Depending
+ * on whether or not the display info contains an override type, we may have to call different
+ * methods on [MobileMappingsProxy] to generate an icon lookup key.
+ */
+sealed interface ResolvedNetworkType {
+ @NetworkType val type: Int
+}
+
+data class DefaultNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
+
+data class OverrideNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
new file mode 100644
index 0000000..45284cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
+import android.telephony.TelephonyManager
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import java.lang.IllegalStateException
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Every mobile line of service can be identified via a [SubscriptionInfo] object. We set up a
+ * repository for each individual, tracked subscription via [MobileConnectionsRepository], and this
+ * repository is responsible for setting up a [TelephonyManager] object tied to its subscriptionId
+ *
+ * There should only ever be one [MobileConnectionRepository] per subscription, since
+ * [TelephonyManager] limits the number of callbacks that can be registered per process.
+ *
+ * This repository should have all of the relevant information for a single line of service, which
+ * eventually becomes a single icon in the status bar.
+ */
+interface MobileConnectionRepository {
+ /**
+ * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
+ * listener + model.
+ */
+ val subscriptionModelFlow: Flow<MobileSubscriptionModel>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class MobileConnectionRepositoryImpl(
+ private val subId: Int,
+ telephonyManager: TelephonyManager,
+ bgDispatcher: CoroutineDispatcher,
+ logger: ConnectivityPipelineLogger,
+ scope: CoroutineScope,
+) : MobileConnectionRepository {
+ init {
+ if (telephonyManager.subscriptionId != subId) {
+ throw IllegalStateException(
+ "TelephonyManager should be created with subId($subId). " +
+ "Found ${telephonyManager.subscriptionId} instead."
+ )
+ }
+ }
+
+ override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
+ var state = MobileSubscriptionModel()
+ conflatedCallbackFlow {
+ // TODO (b/240569788): log all of these into the connectivity logger
+ val callback =
+ object :
+ TelephonyCallback(),
+ TelephonyCallback.ServiceStateListener,
+ TelephonyCallback.SignalStrengthsListener,
+ TelephonyCallback.DataConnectionStateListener,
+ TelephonyCallback.DataActivityListener,
+ TelephonyCallback.CarrierNetworkListener,
+ TelephonyCallback.DisplayInfoListener {
+ override fun onServiceStateChanged(serviceState: ServiceState) {
+ state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+ trySend(state)
+ }
+
+ override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+ val cdmaLevel =
+ signalStrength
+ .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
+ .let { strengths ->
+ if (!strengths.isEmpty()) {
+ strengths[0].level
+ } else {
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+ }
+ }
+
+ val primaryLevel = signalStrength.level
+
+ state =
+ state.copy(
+ cdmaLevel = cdmaLevel,
+ primaryLevel = primaryLevel,
+ isGsm = signalStrength.isGsm,
+ )
+ trySend(state)
+ }
+
+ override fun onDataConnectionStateChanged(
+ dataState: Int,
+ networkType: Int
+ ) {
+ state = state.copy(dataConnectionState = dataState)
+ trySend(state)
+ }
+
+ override fun onDataActivity(direction: Int) {
+ state = state.copy(dataActivityDirection = direction)
+ trySend(state)
+ }
+
+ override fun onCarrierNetworkChange(active: Boolean) {
+ state = state.copy(carrierNetworkChangeActive = active)
+ trySend(state)
+ }
+
+ override fun onDisplayInfoChanged(
+ telephonyDisplayInfo: TelephonyDisplayInfo
+ ) {
+ val networkType =
+ if (
+ telephonyDisplayInfo.overrideNetworkType ==
+ OVERRIDE_NETWORK_TYPE_NONE
+ ) {
+ DefaultNetworkType(telephonyDisplayInfo.networkType)
+ } else {
+ OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType)
+ }
+ state = state.copy(resolvedNetworkType = networkType)
+ trySend(state)
+ }
+ }
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ .onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), state)
+ }
+
+ class Factory
+ @Inject
+ constructor(
+ private val telephonyManager: TelephonyManager,
+ private val logger: ConnectivityPipelineLogger,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+ ) {
+ fun build(subId: Int): MobileConnectionRepository {
+ return MobileConnectionRepositoryImpl(
+ subId,
+ telephonyManager.createForSubscriptionId(subId),
+ bgDispatcher,
+ logger,
+ scope,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
new file mode 100644
index 0000000..0e2428a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.content.Context
+import android.content.IntentFilter
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyManager
+import androidx.annotation.VisibleForTesting
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
+ * on various policy
+ */
+interface MobileConnectionsRepository {
+ /** Observable list of current mobile subscriptions */
+ val subscriptionsFlow: Flow<List<SubscriptionInfo>>
+
+ /** Observable for the subscriptionId of the current mobile data connection */
+ val activeMobileDataSubscriptionId: Flow<Int>
+
+ /** Observable for [MobileMappings.Config] tracking the defaults */
+ val defaultDataSubRatConfig: StateFlow<Config>
+
+ /** Get or create a repository for the line of service for the given subscription ID */
+ fun getRepoForSubId(subId: Int): MobileConnectionRepository
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileConnectionsRepositoryImpl
+@Inject
+constructor(
+ private val subscriptionManager: SubscriptionManager,
+ private val telephonyManager: TelephonyManager,
+ private val logger: ConnectivityPipelineLogger,
+ broadcastDispatcher: BroadcastDispatcher,
+ private val context: Context,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+ private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
+) : MobileConnectionsRepository {
+ private val subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
+
+ /**
+ * State flow that emits the set of mobile data subscriptions, each represented by its own
+ * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
+ * info object, but for now we keep track of the infos themselves.
+ */
+ override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
+ conflatedCallbackFlow {
+ val callback =
+ object : SubscriptionManager.OnSubscriptionsChangedListener() {
+ override fun onSubscriptionsChanged() {
+ trySend(Unit)
+ }
+ }
+
+ subscriptionManager.addOnSubscriptionsChangedListener(
+ bgDispatcher.asExecutor(),
+ callback,
+ )
+
+ awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+ }
+ .mapLatest { fetchSubscriptionsList() }
+ .onEach { infos -> dropUnusedReposFromCache(infos) }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
+
+ /** StateFlow that keeps track of the current active mobile data subscription */
+ override val activeMobileDataSubscriptionId: StateFlow<Int> =
+ conflatedCallbackFlow {
+ val callback =
+ object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
+ override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+ trySend(subId)
+ }
+ }
+
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ )
+
+ private val defaultDataSubChangedEvent =
+ broadcastDispatcher.broadcastFlow(
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ )
+
+ private val carrierConfigChangedEvent =
+ broadcastDispatcher.broadcastFlow(
+ IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+ )
+
+ /**
+ * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
+ * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
+ * config, so this will apply to every icon that we care about.
+ *
+ * Relevant bits in the config are things like
+ * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
+ *
+ * This flow will produce whenever the default data subscription or the carrier config changes.
+ */
+ override val defaultDataSubRatConfig: StateFlow<Config> =
+ combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ ->
+ Config.readConfig(context)
+ }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ initialValue = Config.readConfig(context)
+ )
+
+ override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
+ if (!isValidSubId(subId)) {
+ throw IllegalArgumentException(
+ "subscriptionId $subId is not in the list of valid subscriptions"
+ )
+ }
+
+ return subIdRepositoryCache[subId]
+ ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
+ }
+
+ private fun isValidSubId(subId: Int): Boolean {
+ subscriptionsFlow.value.forEach {
+ if (it.subscriptionId == subId) {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
+
+ private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
+ return mobileConnectionRepositoryFactory.build(subId)
+ }
+
+ private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
+ // Remove any connection repository from the cache that isn't in the new set of IDs. They
+ // will get garbage collected once their subscribers go away
+ val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
+
+ subIdRepositoryCache.keys.forEach {
+ if (!currentValidSubscriptionIds.contains(it)) {
+ subIdRepositoryCache.remove(it)
+ }
+ }
+ }
+
+ private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
+ withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
deleted file mode 100644
index 36de2a2..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
-
-import android.telephony.CellSignalStrength
-import android.telephony.CellSignalStrengthCdma
-import android.telephony.ServiceState
-import android.telephony.SignalStrength
-import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
-import android.telephony.TelephonyCallback
-import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
-import android.telephony.TelephonyCallback.CarrierNetworkListener
-import android.telephony.TelephonyCallback.DataActivityListener
-import android.telephony.TelephonyCallback.DataConnectionStateListener
-import android.telephony.TelephonyCallback.DisplayInfoListener
-import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyCallback.SignalStrengthsListener
-import android.telephony.TelephonyDisplayInfo
-import android.telephony.TelephonyManager
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.asExecutor
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
-
-/**
- * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
- * on various policy
- */
-interface MobileSubscriptionRepository {
- /** Observable list of current mobile subscriptions */
- val subscriptionsFlow: Flow<List<SubscriptionInfo>>
-
- /** Observable for the subscriptionId of the current mobile data connection */
- val activeMobileDataSubscriptionId: Flow<Int>
-
- /** Get or create an observable for the given subscription ID */
- fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
-}
-
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class MobileSubscriptionRepositoryImpl
-@Inject
-constructor(
- private val subscriptionManager: SubscriptionManager,
- private val telephonyManager: TelephonyManager,
- @Background private val bgDispatcher: CoroutineDispatcher,
- @Application private val scope: CoroutineScope,
-) : MobileSubscriptionRepository {
- private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = mutableMapOf()
-
- /**
- * State flow that emits the set of mobile data subscriptions, each represented by its own
- * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
- * info object, but for now we keep track of the infos themselves.
- */
- override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
- conflatedCallbackFlow {
- val callback =
- object : SubscriptionManager.OnSubscriptionsChangedListener() {
- override fun onSubscriptionsChanged() {
- trySend(Unit)
- }
- }
-
- subscriptionManager.addOnSubscriptionsChangedListener(
- bgDispatcher.asExecutor(),
- callback,
- )
-
- awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
- }
- .mapLatest { fetchSubscriptionsList() }
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
-
- /** StateFlow that keeps track of the current active mobile data subscription */
- override val activeMobileDataSubscriptionId: StateFlow<Int> =
- conflatedCallbackFlow {
- val callback =
- object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
- override fun onActiveDataSubscriptionIdChanged(subId: Int) {
- trySend(subId)
- }
- }
-
- telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
- awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
- }
- .stateIn(
- scope,
- started = SharingStarted.WhileSubscribed(),
- SubscriptionManager.INVALID_SUBSCRIPTION_ID
- )
-
- /**
- * Each mobile subscription needs its own flow, which comes from registering listeners on the
- * system. Use this method to create those flows and cache them for reuse
- */
- override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
- return subIdFlowCache[subId]
- ?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
- }
-
- @VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache
-
- private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
- var state = MobileSubscriptionModel()
- conflatedCallbackFlow {
- val phony = telephonyManager.createForSubscriptionId(subId)
- // TODO (b/240569788): log all of these into the connectivity logger
- val callback =
- object :
- TelephonyCallback(),
- ServiceStateListener,
- SignalStrengthsListener,
- DataConnectionStateListener,
- DataActivityListener,
- CarrierNetworkListener,
- DisplayInfoListener {
- override fun onServiceStateChanged(serviceState: ServiceState) {
- state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
- trySend(state)
- }
- override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
- val cdmaLevel =
- signalStrength
- .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
- .let { strengths ->
- if (!strengths.isEmpty()) {
- strengths[0].level
- } else {
- CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
- }
- }
-
- val primaryLevel = signalStrength.level
-
- state =
- state.copy(
- cdmaLevel = cdmaLevel,
- primaryLevel = primaryLevel,
- isGsm = signalStrength.isGsm,
- )
- trySend(state)
- }
- override fun onDataConnectionStateChanged(
- dataState: Int,
- networkType: Int
- ) {
- state = state.copy(dataConnectionState = dataState)
- trySend(state)
- }
- override fun onDataActivity(direction: Int) {
- state = state.copy(dataActivityDirection = direction)
- trySend(state)
- }
- override fun onCarrierNetworkChange(active: Boolean) {
- state = state.copy(carrierNetworkChangeActive = active)
- trySend(state)
- }
- override fun onDisplayInfoChanged(
- telephonyDisplayInfo: TelephonyDisplayInfo
- ) {
- state = state.copy(displayInfo = telephonyDisplayInfo)
- trySend(state)
- }
- }
- phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
- awaitClose {
- phony.unregisterTelephonyCallback(callback)
- // Release the cached flow
- subIdFlowCache.remove(subId)
- }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), state)
- }
-
- private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
- withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 40fe0f3..15f4acc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -17,32 +17,58 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CarrierConfigManager
-import com.android.settingslib.SignalIcon
-import com.android.settingslib.mobile.TelephonyIcons
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
interface MobileIconInteractor {
- /** Identifier for RAT type indicator */
- val iconGroup: Flow<SignalIcon.MobileIconGroup>
+ /** Observable for RAT type (network type) indicator */
+ val networkTypeIconGroup: Flow<MobileIconGroup>
+
/** True if this line of service is emergency-only */
val isEmergencyOnly: Flow<Boolean>
+
/** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
val level: Flow<Int>
+
/** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
val numberOfLevels: Flow<Int>
+
/** True when we want to draw an icon that makes room for the exclamation mark */
val cutOut: Flow<Boolean>
}
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
class MobileIconInteractorImpl(
- mobileStatusInfo: Flow<MobileSubscriptionModel>,
+ defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>,
+ defaultMobileIconGroup: Flow<MobileIconGroup>,
+ mobileMappingsProxy: MobileMappingsProxy,
+ connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
- override val iconGroup: Flow<SignalIcon.MobileIconGroup> = flowOf(TelephonyIcons.THREE_G)
+ private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
+
+ /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
+ override val networkTypeIconGroup: Flow<MobileIconGroup> =
+ combine(
+ mobileStatusInfo,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ ) { info, mapping, defaultGroup ->
+ val lookupKey =
+ when (val resolved = info.resolvedNetworkType) {
+ is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
+ is OverrideNetworkType -> mobileMappingsProxy.toIconKeyOverride(resolved.type)
+ }
+ mapping[lookupKey] ?: defaultGroup
+ }
+
override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
override val level: Flow<Int> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 8e67e19..cd411a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -19,29 +19,51 @@
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
- * Business layer logic for mobile subscription icons
+ * Business layer logic for the set of mobile subscription icons.
*
- * Mobile indicators represent the UI for the (potentially filtered) list of [SubscriptionInfo]s
- * that the system knows about. They obey policy that depends on OEM, carrier, and locale configs
+ * This interactor represents known set of mobile subscriptions (represented by [SubscriptionInfo]).
+ * The list of subscriptions is filtered based on the opportunistic flags on the infos.
+ *
+ * It provides the default mapping between the telephony display info and the icon group that
+ * represents each RAT (LTE, 3G, etc.), as well as can produce an interactor for each individual
+ * icon
*/
+interface MobileIconsInteractor {
+ val filteredSubscriptions: Flow<List<SubscriptionInfo>>
+ val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
+ val defaultMobileIconGroup: Flow<MobileIconGroup>
+ val isUserSetup: Flow<Boolean>
+ fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
+}
+
@SysUISingleton
-class MobileIconsInteractor
+class MobileIconsInteractorImpl
@Inject
constructor(
- private val mobileSubscriptionRepo: MobileSubscriptionRepository,
+ private val mobileSubscriptionRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker,
+ private val mobileMappingsProxy: MobileMappingsProxy,
userSetupRepo: UserSetupRepository,
-) {
+ @Application private val scope: CoroutineScope,
+) : MobileIconsInteractor {
private val activeMobileDataSubscriptionId =
mobileSubscriptionRepo.activeMobileDataSubscriptionId
@@ -61,7 +83,7 @@
* [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
* and by checking which subscription is opportunistic, or which one is active.
*/
- val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
+ override val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
->
// Based on the old logic,
@@ -92,15 +114,29 @@
}
}
- val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+ /**
+ * Mapping from network type to [MobileIconGroup] using the config generated for the default
+ * subscription Id. This mapping is the same for every subscription.
+ */
+ override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
+ mobileSubscriptionRepo.defaultDataSubRatConfig
+ .map { mobileMappingsProxy.mapIconSets(it) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
+
+ /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
+ override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
+ mobileSubscriptionRepo.defaultDataSubRatConfig
+ .map { mobileMappingsProxy.getDefaultIcons(it) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
+
+ override val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
/** Vends out new [MobileIconInteractor] for a particular subId */
- fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
- MobileIconInteractorImpl(mobileSubscriptionFlowForSubId(subId))
-
- /**
- * Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections
- */
- private fun mobileSubscriptionFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> =
- mobileSubscriptionRepo.getFlowForSubId(subId)
+ override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ MobileIconInteractorImpl(
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ mobileMappingsProxy,
+ mobileSubscriptionRepo.getRepoForSubId(subId),
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 1405b05..67ea139 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.binder
import android.content.res.ColorStateList
+import android.view.View.GONE
+import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.view.isVisible
@@ -24,6 +26,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.R
+import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
import kotlinx.coroutines.flow.collect
@@ -37,6 +40,7 @@
view: ViewGroup,
viewModel: MobileIconViewModel,
) {
+ val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
@@ -52,10 +56,20 @@
}
}
+ // Set the network type icon
+ launch {
+ viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
+ dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
+ networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+ }
+ }
+
// Set the tint
launch {
viewModel.tint.collect { tint ->
- iconView.imageTintList = ColorStateList.valueOf(tint)
+ val tintList = ColorStateList.valueOf(tint)
+ iconView.imageTintList = tintList
+ networkTypeView.imageTintList = tintList
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index cfabeba..cc8f6dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -18,6 +18,8 @@
import android.graphics.Color
import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
@@ -26,6 +28,7 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
/**
* View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -54,5 +57,15 @@
.distinctUntilChanged()
.logOutputChange(logger, "iconId($subscriptionId)")
+ /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
+ var networkTypeIcon: Flow<Icon?> =
+ iconInteractor.networkTypeIconGroup.map {
+ val desc =
+ if (it.dataContentDescription != 0)
+ ContentDescription.Resource(it.dataContentDescription)
+ else null
+ Icon.Resource(it.dataType, desc)
+ }
+
var tint: Flow<Int> = flowOf(Color.CYAN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
new file mode 100644
index 0000000..60bd038
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.util
+
+import android.telephony.Annotation.NetworkType
+import android.telephony.TelephonyDisplayInfo
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.MobileMappings.Config
+import javax.inject.Inject
+
+/**
+ * [MobileMappings] owns the logic on creating the map from [TelephonyDisplayInfo] to
+ * [MobileIconGroup]. It creates that hash map and also manages the creation of lookup keys. This
+ * interface allows us to proxy those calls to the static java methods in SettingsLib and also fake
+ * them out in tests
+ */
+interface MobileMappingsProxy {
+ fun mapIconSets(config: Config): Map<String, MobileIconGroup>
+ fun getDefaultIcons(config: Config): MobileIconGroup
+ fun toIconKey(@NetworkType networkType: Int): String
+ fun toIconKeyOverride(@NetworkType networkType: Int): String
+}
+
+/** Injectable wrapper class for [MobileMappings] */
+class MobileMappingsProxyImpl @Inject constructor() : MobileMappingsProxy {
+ override fun mapIconSets(config: Config): Map<String, MobileIconGroup> =
+ MobileMappings.mapIconSets(config)
+
+ override fun getDefaultIcons(config: Config): MobileIconGroup =
+ MobileMappings.getDefaultIcons(config)
+
+ override fun toIconKey(@NetworkType networkType: Int): String =
+ MobileMappings.toIconKey(networkType)
+
+ override fun toIconKeyOverride(networkType: Int): String =
+ MobileMappings.toDisplayIconKey(networkType)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index 28a9b97..cf4106c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -61,7 +61,7 @@
* animation to and from the parent dialog.
*/
@JvmOverloads
- fun onUserListItemClicked(
+ open fun onUserListItemClicked(
record: UserRecord,
dialogShower: DialogShower? = null,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index cd7bd2d..b8930a4 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -204,5 +204,4 @@
}
}
-const val SENDER_TAG = "MediaTapToTransferSender"
private const val ANIMATION_DURATION = 500L
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
new file mode 100644
index 0000000..9653985
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.android.systemui.util.kotlin
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import java.util.function.Consumer
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+
+/**
+ * Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to
+ * [LifeCycle.State.CREATED] to better align with legacy ViewController usage of attaching listeners
+ * during onViewAttached() and removing during onViewRemoved()
+ */
+@JvmOverloads
+fun <T> collectFlow(
+ view: View,
+ flow: Flow<T>,
+ consumer: Consumer<T>,
+ state: Lifecycle.State = Lifecycle.State.CREATED,
+) {
+ view.repeatWhenAttached { repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 42d7d52..44f6d03 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -47,7 +47,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.wallpapers.canvas.WallpaperColorExtractor;
+import com.android.systemui.wallpapers.canvas.WallpaperLocalColorExtractor;
import com.android.systemui.wallpapers.gl.EglHelper;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
@@ -521,7 +521,7 @@
class CanvasEngine extends WallpaperService.Engine implements DisplayListener {
private WallpaperManager mWallpaperManager;
- private final WallpaperColorExtractor mWallpaperColorExtractor;
+ private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
private SurfaceHolder mSurfaceHolder;
@VisibleForTesting
static final int MIN_SURFACE_WIDTH = 128;
@@ -543,9 +543,9 @@
super();
setFixedSizeAllowed(true);
setShowForAllUsers(true);
- mWallpaperColorExtractor = new WallpaperColorExtractor(
+ mWallpaperLocalColorExtractor = new WallpaperLocalColorExtractor(
mBackgroundExecutor,
- new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
@Override
public void onColorsProcessed(List<RectF> regions,
List<WallpaperColors> colors) {
@@ -570,7 +570,7 @@
// if the number of pages is already computed, transmit it to the color extractor
if (mPagesComputed) {
- mWallpaperColorExtractor.onPageChanged(mPages);
+ mWallpaperLocalColorExtractor.onPageChanged(mPages);
}
}
@@ -597,7 +597,7 @@
public void onDestroy() {
getDisplayContext().getSystemService(DisplayManager.class)
.unregisterDisplayListener(this);
- mWallpaperColorExtractor.cleanUp();
+ mWallpaperLocalColorExtractor.cleanUp();
unloadBitmap();
}
@@ -813,7 +813,7 @@
@VisibleForTesting
void recomputeColorExtractorMiniBitmap() {
- mWallpaperColorExtractor.onBitmapChanged(mBitmap);
+ mWallpaperLocalColorExtractor.onBitmapChanged(mBitmap);
}
@VisibleForTesting
@@ -830,14 +830,14 @@
public void addLocalColorsAreas(@NonNull List<RectF> regions) {
// this call will activate the offset notifications
// if no colors were being processed before
- mWallpaperColorExtractor.addLocalColorsAreas(regions);
+ mWallpaperLocalColorExtractor.addLocalColorsAreas(regions);
}
@Override
public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
// this call will deactivate the offset notifications
// if we are no longer processing colors
- mWallpaperColorExtractor.removeLocalColorAreas(regions);
+ mWallpaperLocalColorExtractor.removeLocalColorAreas(regions);
}
@Override
@@ -853,7 +853,7 @@
if (pages != mPages || !mPagesComputed) {
mPages = pages;
mPagesComputed = true;
- mWallpaperColorExtractor.onPageChanged(mPages);
+ mWallpaperLocalColorExtractor.onPageChanged(mPages);
}
}
@@ -881,7 +881,7 @@
.getSystemService(WindowManager.class)
.getCurrentWindowMetrics()
.getBounds();
- mWallpaperColorExtractor.setDisplayDimensions(window.width(), window.height());
+ mWallpaperLocalColorExtractor.setDisplayDimensions(window.width(), window.height());
}
@@ -902,7 +902,7 @@
: mBitmap.isRecycled() ? "recycled"
: mBitmap.getWidth() + "x" + mBitmap.getHeight());
- mWallpaperColorExtractor.dump(prefix, fd, out, args);
+ mWallpaperLocalColorExtractor.dump(prefix, fd, out, args);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractor.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
rename to packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractor.java
index e2e4555..6cac5c9 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractor.java
@@ -45,14 +45,14 @@
* It uses a background executor, and uses callbacks to inform that the work is done.
* It uses a downscaled version of the wallpaper to extract the colors.
*/
-public class WallpaperColorExtractor {
+public class WallpaperLocalColorExtractor {
private Bitmap mMiniBitmap;
@VisibleForTesting
static final int SMALL_SIDE = 128;
- private static final String TAG = WallpaperColorExtractor.class.getSimpleName();
+ private static final String TAG = WallpaperLocalColorExtractor.class.getSimpleName();
private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
new RectF(0, 0, 1, 1);
@@ -70,12 +70,12 @@
@Background
private final Executor mBackgroundExecutor;
- private final WallpaperColorExtractorCallback mWallpaperColorExtractorCallback;
+ private final WallpaperLocalColorExtractorCallback mWallpaperLocalColorExtractorCallback;
/**
* Interface to handle the callbacks after the different steps of the color extraction
*/
- public interface WallpaperColorExtractorCallback {
+ public interface WallpaperLocalColorExtractorCallback {
/**
* Callback after the colors of new regions have been extracted
* @param regions the list of new regions that have been processed
@@ -103,13 +103,13 @@
/**
* Creates a new color extractor.
* @param backgroundExecutor the executor on which the color extraction will be performed
- * @param wallpaperColorExtractorCallback an interface to handle the callbacks from
+ * @param wallpaperLocalColorExtractorCallback an interface to handle the callbacks from
* the color extractor.
*/
- public WallpaperColorExtractor(@Background Executor backgroundExecutor,
- WallpaperColorExtractorCallback wallpaperColorExtractorCallback) {
+ public WallpaperLocalColorExtractor(@Background Executor backgroundExecutor,
+ WallpaperLocalColorExtractorCallback wallpaperLocalColorExtractorCallback) {
mBackgroundExecutor = backgroundExecutor;
- mWallpaperColorExtractorCallback = wallpaperColorExtractorCallback;
+ mWallpaperLocalColorExtractorCallback = wallpaperLocalColorExtractorCallback;
}
/**
@@ -157,7 +157,7 @@
mBitmapWidth = bitmap.getWidth();
mBitmapHeight = bitmap.getHeight();
mMiniBitmap = createMiniBitmap(bitmap);
- mWallpaperColorExtractorCallback.onMiniBitmapUpdated();
+ mWallpaperLocalColorExtractorCallback.onMiniBitmapUpdated();
recomputeColors();
}
}
@@ -206,7 +206,7 @@
boolean wasActive = isActive();
mPendingRegions.addAll(regions);
if (!wasActive && isActive()) {
- mWallpaperColorExtractorCallback.onActivated();
+ mWallpaperLocalColorExtractorCallback.onActivated();
}
processColorsInternal();
}
@@ -228,7 +228,7 @@
mPendingRegions.removeAll(regions);
regions.forEach(mProcessedRegions::remove);
if (wasActive && !isActive()) {
- mWallpaperColorExtractorCallback.onDeactivated();
+ mWallpaperLocalColorExtractorCallback.onDeactivated();
}
}
}
@@ -252,7 +252,7 @@
}
private Bitmap createMiniBitmap(@NonNull Bitmap bitmap) {
- Trace.beginSection("WallpaperColorExtractor#createMiniBitmap");
+ Trace.beginSection("WallpaperLocalColorExtractor#createMiniBitmap");
// if both sides of the image are larger than SMALL_SIDE, downscale the bitmap.
int smallestSide = Math.min(bitmap.getWidth(), bitmap.getHeight());
float scale = Math.min(1.0f, (float) SMALL_SIDE / smallestSide);
@@ -359,7 +359,7 @@
*/
if (mDisplayWidth < 0 || mDisplayHeight < 0 || mPages < 0) return;
- Trace.beginSection("WallpaperColorExtractor#processColorsInternal");
+ Trace.beginSection("WallpaperLocalColorExtractor#processColorsInternal");
List<WallpaperColors> processedColors = new ArrayList<>();
for (int i = 0; i < mPendingRegions.size(); i++) {
RectF nextArea = mPendingRegions.get(i);
@@ -372,7 +372,7 @@
mPendingRegions.clear();
Trace.endSection();
- mWallpaperColorExtractorCallback.onColorsProcessed(processedRegions, processedColors);
+ mWallpaperLocalColorExtractorCallback.onColorsProcessed(processedRegions, processedColors);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 309f168..02738d5 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -49,6 +49,7 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.notetask.NoteTaskInitializer;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.statusbar.CommandQueue;
@@ -58,7 +59,6 @@
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -113,7 +113,6 @@
private final Optional<Pip> mPipOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
- private final Optional<FloatingTasks> mFloatingTasksOptional;
private final Optional<DesktopMode> mDesktopModeOptional;
private final CommandQueue mCommandQueue;
@@ -125,6 +124,7 @@
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final ProtoTracer mProtoTracer;
private final UserTracker mUserTracker;
+ private final NoteTaskInitializer mNoteTaskInitializer;
private final Executor mSysUiMainExecutor;
// Listeners and callbacks. Note that we prefer member variable over anonymous class here to
@@ -176,7 +176,6 @@
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
- Optional<FloatingTasks> floatingTasksOptional,
Optional<DesktopMode> desktopMode,
CommandQueue commandQueue,
ConfigurationController configurationController,
@@ -187,6 +186,7 @@
ProtoTracer protoTracer,
WakefulnessLifecycle wakefulnessLifecycle,
UserTracker userTracker,
+ NoteTaskInitializer noteTaskInitializer,
@Main Executor sysUiMainExecutor) {
mContext = context;
mShell = shell;
@@ -203,7 +203,7 @@
mWakefulnessLifecycle = wakefulnessLifecycle;
mProtoTracer = protoTracer;
mUserTracker = userTracker;
- mFloatingTasksOptional = floatingTasksOptional;
+ mNoteTaskInitializer = noteTaskInitializer;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -226,6 +226,8 @@
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
mDesktopModeOptional.ifPresent(this::initDesktopMode);
+
+ mNoteTaskInitializer.initialize();
}
@VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt
new file mode 100644
index 0000000..6c5620d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.keyguard
+
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.settings.GlobalSettings
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FaceWakeUpTriggersConfigTest : SysuiTestCase() {
+ @Mock lateinit var globalSettings: GlobalSettings
+ @Mock lateinit var dumpManager: DumpManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testShouldTriggerFaceAuthOnWakeUpFrom_inConfig_returnsTrue() {
+ val faceWakeUpTriggersConfig =
+ createFaceWakeUpTriggersConfig(
+ intArrayOf(PowerManager.WAKE_REASON_POWER_BUTTON, PowerManager.WAKE_REASON_GESTURE)
+ )
+
+ assertTrue(
+ faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(
+ PowerManager.WAKE_REASON_POWER_BUTTON
+ )
+ )
+ assertTrue(
+ faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(
+ PowerManager.WAKE_REASON_GESTURE
+ )
+ )
+ assertFalse(
+ faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(
+ PowerManager.WAKE_REASON_APPLICATION
+ )
+ )
+ }
+
+ private fun createFaceWakeUpTriggersConfig(wakeUpTriggers: IntArray): FaceWakeUpTriggersConfig {
+ overrideResource(
+ com.android.systemui.R.array.config_face_auth_wake_up_triggers,
+ wakeUpTriggers
+ )
+
+ return FaceWakeUpTriggersConfig(mContext.getResources(), globalSettings, dumpManager)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 627d738..61c7bb5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -44,7 +44,6 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
@@ -105,8 +104,6 @@
private FrameLayout mLargeClockFrame;
@Mock
private SecureSettings mSecureSettings;
- @Mock
- private FeatureFlags mFeatureFlags;
private final View mFakeSmartspaceView = new View(mContext);
@@ -143,8 +140,7 @@
mSecureSettings,
mExecutor,
mDumpManager,
- mClockEventController,
- mFeatureFlags
+ mClockEventController
);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c6233b5..66be6ec 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -110,6 +110,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.util.settings.GlobalSettings;
import org.junit.After;
import org.junit.Assert;
@@ -210,6 +211,9 @@
private UiEventLogger mUiEventLogger;
@Mock
private PowerManager mPowerManager;
+ @Mock
+ private GlobalSettings mGlobalSettings;
+ private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -237,8 +241,7 @@
when(mActivityService.getCurrentUser()).thenReturn(mCurrentUserInfo);
when(mActivityService.getCurrentUserId()).thenReturn(mCurrentUserId);
when(mFaceManager.isHardwareDetected()).thenReturn(true);
- when(mFaceManager.hasEnrolledTemplates()).thenReturn(true);
- when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
@@ -294,6 +297,12 @@
.when(ActivityManager::getCurrentUser);
ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService);
+ mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfig(
+ mContext.getResources(),
+ mGlobalSettings,
+ mDumpManager
+ );
+
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
@@ -593,7 +602,7 @@
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
verify(mFaceManager).isHardwareDetected();
- verify(mFaceManager).hasEnrolledTemplates(anyInt());
+ verify(mFaceManager, never()).hasEnrolledTemplates(anyInt());
}
@Test
@@ -607,16 +616,22 @@
@Test
public void testTriesToAuthenticate_whenKeyguard() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
- mTestableLooper.processAllMessages();
keyguardIsVisible();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+ verify(mUiEventLogger).logWithInstanceIdAndPosition(
+ eq(FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP),
+ eq(0),
+ eq(null),
+ any(),
+ eq(PowerManager.WAKE_REASON_POWER_BUTTON));
}
@Test
public void skipsAuthentication_whenStatusBarShadeLocked() {
mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -630,7 +645,7 @@
STRONG_AUTH_REQUIRED_AFTER_BOOT);
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -684,7 +699,7 @@
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
@@ -709,7 +724,7 @@
@Test
public void testTriesToAuthenticate_whenTrustOnAgentKeyguard_ifBypass() {
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
when(mKeyguardBypassController.canBypass()).thenReturn(true);
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
@@ -721,7 +736,7 @@
@Test
public void testIgnoresAuth_whenTrustAgentOnKeyguard_withoutBypass() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
@@ -732,7 +747,7 @@
@Test
public void testIgnoresAuth_whenLockdown() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
@@ -744,7 +759,7 @@
@Test
public void testTriesToAuthenticate_whenLockout() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
@@ -768,7 +783,7 @@
@Test
public void testFaceAndFingerprintLockout_onlyFace() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -779,7 +794,7 @@
@Test
public void testFaceAndFingerprintLockout_onlyFingerprint() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -791,7 +806,7 @@
@Test
public void testFaceAndFingerprintLockout() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -890,7 +905,7 @@
when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
.thenReturn(faceLockoutMode);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -1064,7 +1079,7 @@
@Test
public void testOccludingAppFingerprintListeningState() {
// GIVEN keyguard isn't visible (app occluding)
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
@@ -1079,7 +1094,7 @@
@Test
public void testOccludingAppRequestsFingerprint() {
// GIVEN keyguard isn't visible (app occluding)
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
// WHEN an occluding app requests fp
@@ -1170,7 +1185,7 @@
biometricsNotDisabledThroughDevicePolicyManager();
mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
setKeyguardBouncerVisibility(false /* isVisible */);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
when(mKeyguardBypassController.canBypass()).thenReturn(true);
keyguardIsVisible();
@@ -1549,7 +1564,7 @@
@Test
public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -1598,6 +1613,36 @@
verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
}
+ @Test
+ public void testDreamingStopped_faceDoesNotRun() {
+ mKeyguardUpdateMonitor.dispatchDreamingStopped();
+ mTestableLooper.processAllMessages();
+
+ verify(mFaceManager, never()).authenticate(
+ any(), any(), any(), any(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testFaceWakeupTrigger_runFaceAuth_onlyOnConfiguredTriggers() {
+ // keyguard is visible
+ keyguardIsVisible();
+
+ // WHEN device wakes up from an application
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_APPLICATION);
+ mTestableLooper.processAllMessages();
+
+ // THEN face auth isn't triggered
+ verify(mFaceManager, never()).authenticate(
+ any(), any(), any(), any(), anyInt(), anyBoolean());
+
+ // WHEN device wakes up from the power button
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+
+ // THEN face auth is triggered
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+ }
+
private void cleanupKeyguardUpdateMonitor() {
if (mKeyguardUpdateMonitor != null) {
mKeyguardUpdateMonitor.removeCallback(mTestCallback);
@@ -1718,7 +1763,7 @@
}
private void deviceIsInteractive() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
}
private void bouncerFullyVisible() {
@@ -1768,7 +1813,8 @@
mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker,
mPowerManager, mTrustManager, mSubscriptionManager, mUserManager,
mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
- mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager);
+ mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
+ mFaceWakeUpTriggersConfig);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
new file mode 100644
index 0000000..ae8f419
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2022 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.keyguard;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedStateListDrawable;
+import android.util.Pair;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.doze.util.BurnInHelperKt;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+public class LockIconViewControllerBaseTest extends SysuiTestCase {
+ protected static final String UNLOCKED_LABEL = "unlocked";
+ protected static final int PADDING = 10;
+
+ protected MockitoSession mStaticMockSession;
+
+ protected @Mock LockIconView mLockIconView;
+ protected @Mock AnimatedStateListDrawable mIconDrawable;
+ protected @Mock Context mContext;
+ protected @Mock Resources mResources;
+ protected @Mock(answer = Answers.RETURNS_DEEP_STUBS) WindowManager mWindowManager;
+ protected @Mock StatusBarStateController mStatusBarStateController;
+ protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ protected @Mock KeyguardViewController mKeyguardViewController;
+ protected @Mock KeyguardStateController mKeyguardStateController;
+ protected @Mock FalsingManager mFalsingManager;
+ protected @Mock AuthController mAuthController;
+ protected @Mock DumpManager mDumpManager;
+ protected @Mock AccessibilityManager mAccessibilityManager;
+ protected @Mock ConfigurationController mConfigurationController;
+ protected @Mock VibratorHelper mVibrator;
+ protected @Mock AuthRippleController mAuthRippleController;
+ protected @Mock FeatureFlags mFeatureFlags;
+ protected @Mock KeyguardTransitionRepository mTransitionRepository;
+ protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
+
+ protected LockIconViewController mUnderTest;
+
+ // Capture listeners so that they can be used to send events
+ @Captor protected ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+
+ @Captor protected ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCaptor =
+ ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
+ protected KeyguardStateController.Callback mKeyguardStateCallback;
+
+ @Captor protected ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ protected StatusBarStateController.StateListener mStatusBarStateListener;
+
+ @Captor protected ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
+ protected AuthController.Callback mAuthControllerCallback;
+
+ @Captor protected ArgumentCaptor<KeyguardUpdateMonitorCallback>
+ mKeyguardUpdateMonitorCallbackCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ protected KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+
+ @Captor protected ArgumentCaptor<Point> mPointCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ mStaticMockSession = mockitoSession()
+ .mockStatic(BurnInHelperKt.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+
+ setupLockIconViewMocks();
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
+ Rect windowBounds = new Rect(0, 0, 800, 1200);
+ when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds);
+ when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
+ when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
+ when(mResources.getDimensionPixelSize(R.dimen.lock_icon_padding)).thenReturn(PADDING);
+ when(mAuthController.getScaleFactor()).thenReturn(1f);
+
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+
+ mUnderTest = new LockIconViewController(
+ mLockIconView,
+ mStatusBarStateController,
+ mKeyguardUpdateMonitor,
+ mKeyguardViewController,
+ mKeyguardStateController,
+ mFalsingManager,
+ mAuthController,
+ mDumpManager,
+ mAccessibilityManager,
+ mConfigurationController,
+ mDelayableExecutor,
+ mVibrator,
+ mAuthRippleController,
+ mResources,
+ new KeyguardTransitionInteractor(mTransitionRepository),
+ new KeyguardInteractor(new FakeKeyguardRepository()),
+ mFeatureFlags
+ );
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ protected Pair<Float, Point> setupUdfps() {
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+ final Point udfpsLocation = new Point(50, 75);
+ final float radius = 33f;
+ when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation);
+ when(mAuthController.getUdfpsRadius()).thenReturn(radius);
+
+ return new Pair(radius, udfpsLocation);
+ }
+
+ protected void setupShowLockIcon() {
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
+ }
+
+ protected void captureAuthControllerCallback() {
+ verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
+ mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
+ }
+
+ protected void captureKeyguardStateCallback() {
+ verify(mKeyguardStateController).addCallback(mKeyguardStateCaptor.capture());
+ mKeyguardStateCallback = mKeyguardStateCaptor.getValue();
+ }
+
+ protected void captureStatusBarStateListener() {
+ verify(mStatusBarStateController).addCallback(mStatusBarStateCaptor.capture());
+ mStatusBarStateListener = mStatusBarStateCaptor.getValue();
+ }
+
+ protected void captureKeyguardUpdateMonitorCallback() {
+ verify(mKeyguardUpdateMonitor).registerCallback(
+ mKeyguardUpdateMonitorCallbackCaptor.capture());
+ mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+ }
+
+ protected void setupLockIconViewMocks() {
+ when(mLockIconView.getResources()).thenReturn(mResources);
+ when(mLockIconView.getContext()).thenReturn(mContext);
+ }
+
+ protected void resetLockIconView() {
+ reset(mLockIconView);
+ setupLockIconViewMocks();
+ }
+
+ protected void init(boolean useMigrationFlag) {
+ when(mFeatureFlags.isEnabled(DOZING_MIGRATION_1)).thenReturn(useMigrationFlag);
+ mUnderTest.init();
+
+ verify(mLockIconView, atLeast(1)).addOnAttachStateChangeListener(mAttachCaptor.capture());
+ mAttachCaptor.getValue().onViewAttachedToWindow(mLockIconView);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
new file mode 100644
index 0000000..f4c2284
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.keyguard;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
+import static com.android.keyguard.LockIconView.ICON_LOCK;
+import static com.android.keyguard.LockIconView.ICON_UNLOCK;
+
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Point;
+import android.hardware.biometrics.BiometricSourceType;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.doze.util.BurnInHelperKt;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class LockIconViewControllerTest extends LockIconViewControllerBaseTest {
+
+ @Test
+ public void testUpdateFingerprintLocationOnInit() {
+ // GIVEN fp sensor location is available pre-attached
+ Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
+
+ // WHEN lock icon view controller is initialized and attached
+ init(/* useMigrationFlag= */false);
+
+ // THEN lock icon view location is updated to the udfps location with UDFPS radius
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING));
+ }
+
+ @Test
+ public void testUpdatePaddingBasedOnResolutionScale() {
+ // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5
+ Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
+ when(mAuthController.getScaleFactor()).thenReturn(5f);
+
+ // WHEN lock icon view controller is initialized and attached
+ init(/* useMigrationFlag= */false);
+
+ // THEN lock icon view location is updated with the scaled radius
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING * 5));
+ }
+
+ @Test
+ public void testUpdateLockIconLocationOnAuthenticatorsRegistered() {
+ // GIVEN fp sensor location is not available pre-init
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
+ when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
+ init(/* useMigrationFlag= */false);
+ resetLockIconView(); // reset any method call counts for when we verify method calls later
+
+ // GIVEN fp sensor location is available post-attached
+ captureAuthControllerCallback();
+ Pair<Float, Point> udfps = setupUdfps();
+
+ // WHEN all authenticators are registered
+ mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
+ mDelayableExecutor.runAllReady();
+
+ // THEN lock icon view location is updated with the same coordinates as auth controller vals
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING));
+ }
+
+ @Test
+ public void testUpdateLockIconLocationOnUdfpsLocationChanged() {
+ // GIVEN fp sensor location is not available pre-init
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
+ when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
+ init(/* useMigrationFlag= */false);
+ resetLockIconView(); // reset any method call counts for when we verify method calls later
+
+ // GIVEN fp sensor location is available post-attached
+ captureAuthControllerCallback();
+ Pair<Float, Point> udfps = setupUdfps();
+
+ // WHEN udfps location changes
+ mAuthControllerCallback.onUdfpsLocationChanged();
+ mDelayableExecutor.runAllReady();
+
+ // THEN lock icon view location is updated with the same coordinates as auth controller vals
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING));
+ }
+
+ @Test
+ public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() {
+ // GIVEN Udpfs sensor location is available
+ setupUdfps();
+
+ // WHEN the view is attached
+ init(/* useMigrationFlag= */false);
+
+ // THEN the lock icon view background should be enabled
+ verify(mLockIconView).setUseBackground(true);
+ }
+
+ @Test
+ public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() {
+ // GIVEN Udfps sensor location is not supported
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
+
+ // WHEN the view is attached
+ init(/* useMigrationFlag= */false);
+
+ // THEN the lock icon view background should be disabled
+ verify(mLockIconView).setUseBackground(false);
+ }
+
+ @Test
+ public void testUnlockIconShows_biometricUnlockedTrue() {
+ // GIVEN UDFPS sensor location is available
+ setupUdfps();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ reset(mLockIconView);
+
+ // WHEN face auth's biometric running state changes
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+
+ // THEN the unlock icon is shown
+ verify(mLockIconView).setContentDescription(UNLOCKED_LABEL);
+ }
+
+ @Test
+ public void testLockIconStartState() {
+ // GIVEN lock icon state
+ setupShowLockIcon();
+
+ // WHEN lock icon controller is initialized
+ init(/* useMigrationFlag= */false);
+
+ // THEN the lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, false);
+ }
+
+ @Test
+ public void testLockIcon_updateToUnlock() {
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardStateCallback();
+ reset(mLockIconView);
+
+ // WHEN the unlocked state changes to canDismissLockScreen=true
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
+ mKeyguardStateCallback.onUnlockedChanged();
+
+ // THEN the unlock should show
+ verify(mLockIconView).updateIcon(ICON_UNLOCK, false);
+ }
+
+ @Test
+ public void testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() {
+ // GIVEN udfps not enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false);
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN the dozing state changes
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+
+ // THEN the icon is cleared
+ verify(mLockIconView).clearIcon();
+ }
+
+ @Test
+ public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() {
+ // GIVEN udfps enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN the dozing state changes
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+
+ // THEN the AOD lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, true);
+ }
+
+ @Test
+ public void testBurnInOffsetsUpdated_onDozeAmountChanged() {
+ // GIVEN udfps enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ // GIVEN burn-in offset = 5
+ int burnInOffset = 5;
+ when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset);
+
+ // GIVEN starting state for the lock icon (keyguard)
+ setupShowLockIcon();
+ init(/* useMigrationFlag= */false);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN dozing updates
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+ mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
+
+ // THEN the view's translation is updated to use the AoD burn-in offsets
+ verify(mLockIconView).setTranslationY(burnInOffset);
+ verify(mLockIconView).setTranslationX(burnInOffset);
+ reset(mLockIconView);
+
+ // WHEN the device is no longer dozing
+ mStatusBarStateListener.onDozingChanged(false /* isDozing */);
+ mStatusBarStateListener.onDozeAmountChanged(0f, 0f);
+
+ // THEN the view is updated to NO translation (no burn-in offsets anymore)
+ verify(mLockIconView).setTranslationY(0);
+ verify(mLockIconView).setTranslationX(0);
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..d2c54b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 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.keyguard
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.keyguard.LockIconView.ICON_LOCK
+import com.android.systemui.doze.util.getBurnInOffset
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LockIconViewControllerWithCoroutinesTest : LockIconViewControllerBaseTest() {
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN udfps not enrolled
+ setupUdfps()
+ whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false)
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon()
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */ true)
+ reset(mLockIconView)
+
+ // WHEN the dozing state changes
+ mUnderTest.mIsDozingCallback.accept(true)
+
+ // THEN the icon is cleared
+ verify(mLockIconView).clearIcon()
+ }
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testLockIcon_updateToAodLock_whenUdfpsEnrolled() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN udfps enrolled
+ setupUdfps()
+ whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true)
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon()
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */ true)
+ reset(mLockIconView)
+
+ // WHEN the dozing state changes
+ mUnderTest.mIsDozingCallback.accept(true)
+
+ // THEN the AOD lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, true)
+ }
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testBurnInOffsetsUpdated_onDozeAmountChanged() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN udfps enrolled
+ setupUdfps()
+ whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true)
+
+ // GIVEN burn-in offset = 5
+ val burnInOffset = 5
+ whenever(getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
+
+ // GIVEN starting state for the lock icon (keyguard)
+ setupShowLockIcon()
+ init(/* useMigrationFlag= */ true)
+ reset(mLockIconView)
+
+ // WHEN dozing updates
+ mUnderTest.mIsDozingCallback.accept(true)
+ mUnderTest.mDozeTransitionCallback.accept(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+
+ // THEN the view's translation is updated to use the AoD burn-in offsets
+ verify(mLockIconView).setTranslationY(burnInOffset.toFloat())
+ verify(mLockIconView).setTranslationX(burnInOffset.toFloat())
+ reset(mLockIconView)
+
+ // WHEN the device is no longer dozing
+ mUnderTest.mIsDozingCallback.accept(false)
+ mUnderTest.mDozeTransitionCallback.accept(TransitionStep(AOD, LOCKSCREEN, 0f, FINISHED))
+
+ // THEN the view is updated to NO translation (no burn-in offsets anymore)
+ verify(mLockIconView).setTranslationY(0f)
+ verify(mLockIconView).setTranslationX(0f)
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 19a6c66..77d38c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -35,6 +35,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -68,6 +69,7 @@
public MockitoRule mockito = MockitoJUnit.rule();
private Context mContextWrapper;
+ private AccessibilityManager mAccessibilityManager;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private AccessibilityFloatingMenuController mController;
private AccessibilityButtonTargetsObserver mTargetsObserver;
@@ -87,6 +89,7 @@
}
};
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mLastButtonTargets = Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
mLastButtonMode = Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(),
@@ -348,8 +351,8 @@
mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
final AccessibilityFloatingMenuController controller =
new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
- displayManager, mTargetsObserver, mModeObserver, mKeyguardUpdateMonitor,
- featureFlags);
+ displayManager, mAccessibilityManager, mTargetsObserver, mModeObserver,
+ mKeyguardUpdateMonitor, featureFlags);
controller.init();
return controller;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
new file mode 100644
index 0000000..8ef65dc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.floatingmenu;
+
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.bubbles.DismissView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link DismissAnimationController}. */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DismissAnimationControllerTest extends SysuiTestCase {
+ private DismissAnimationController mDismissAnimationController;
+ private DismissView mDismissView;
+
+ @Before
+ public void setUp() throws Exception {
+ final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
+ stubWindowManager);
+ final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
+ stubMenuViewAppearance);
+ mDismissView = new DismissView(mContext);
+ mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
+ }
+
+ @Test
+ public void showDismissView_success() {
+ mDismissAnimationController.showDismissView(true);
+
+ verify(mDismissView).show();
+ }
+
+ @Test
+ public void hideDismissView_success() {
+ mDismissAnimationController.showDismissView(false);
+
+ verify(mDismissView).hide();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index dbf291c..d0bd4f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -18,9 +18,16 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
import android.graphics.PointF;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -36,6 +43,8 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class MenuAnimationControllerTest extends SysuiTestCase {
+
+ private ViewPropertyAnimator mViewPropertyAnimator;
private MenuView mMenuView;
private MenuAnimationController mMenuAnimationController;
@@ -45,7 +54,11 @@
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
- mMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
+
+ mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+ mViewPropertyAnimator = spy(mMenuView.animate());
+ doReturn(mViewPropertyAnimator).when(mMenuView).animate();
+
mMenuAnimationController = new MenuAnimationController(mMenuView);
}
@@ -58,4 +71,20 @@
assertThat(mMenuView.getTranslationX()).isEqualTo(50);
assertThat(mMenuView.getTranslationY()).isEqualTo(60);
}
+
+ @Test
+ public void startShrinkAnimation_verifyAnimationEndAction() {
+ mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(View.VISIBLE));
+
+ verify(mViewPropertyAnimator).withEndAction(any(Runnable.class));
+ }
+
+ @Test
+ public void startGrowAnimation_menuCompletelyOpaque() {
+ mMenuAnimationController.startShrinkAnimation(null);
+
+ mMenuAnimationController.startGrowAnimation();
+
+ assertThat(mMenuView.getAlpha()).isEqualTo(/* completelyOpaque */ 1.0f);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index bf6d574..78ee627 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -43,6 +43,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -54,6 +55,9 @@
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private DismissAnimationController.DismissCallback mStubDismissCallback;
+
private RecyclerView mStubListView;
private MenuView mMenuView;
private MenuItemAccessibilityDelegate mMenuItemAccessibilityDelegate;
@@ -87,7 +91,7 @@
mMenuItemAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mStubListView, info);
- assertThat(info.getActionList().size()).isEqualTo(5);
+ assertThat(info.getActionList().size()).isEqualTo(6);
}
@Test
@@ -156,6 +160,17 @@
}
@Test
+ public void performRemoveMenuAction_success() {
+ mMenuAnimationController.setDismissCallback(mStubDismissCallback);
+ final boolean removeMenuAction =
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ R.id.action_remove_menu, null);
+
+ assertThat(removeMenuAction).isTrue();
+ verify(mMenuAnimationController).removeMenu();
+ }
+
+ @Test
public void performFocusAction_fadeIn() {
mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
ACTION_ACCESSIBILITY_FOCUS, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index c5b9a29..4acb394 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -21,6 +21,8 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -36,6 +38,7 @@
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.MotionEventHelper;
+import com.android.wm.shell.bubbles.DismissView;
import org.junit.After;
import org.junit.Before;
@@ -57,7 +60,9 @@
private MenuView mStubMenuView;
private MenuListViewTouchHandler mTouchHandler;
private MenuAnimationController mMenuAnimationController;
+ private DismissAnimationController mDismissAnimationController;
private RecyclerView mStubListView;
+ private DismissView mDismissView;
@Before
public void setUp() throws Exception {
@@ -69,7 +74,11 @@
mStubMenuView.setTranslationX(0);
mStubMenuView.setTranslationY(0);
mMenuAnimationController = spy(new MenuAnimationController(mStubMenuView));
- mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController);
+ mDismissView = spy(new DismissView(mContext));
+ mDismissAnimationController =
+ spy(new DismissAnimationController(mDismissView, mStubMenuView));
+ mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
+ mDismissAnimationController);
final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
mStubListView.setAdapter(stubAdapter);
@@ -88,7 +97,9 @@
}
@Test
- public void onActionMoveEvent_shouldMoveToPosition() {
+ public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
+ doReturn(false).when(mDismissAnimationController).maybeConsumeMoveMotionEvent(
+ any(MotionEvent.class));
final int offset = 100;
final MotionEvent stubDownEvent =
mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
@@ -108,6 +119,24 @@
}
@Test
+ public void onActionMoveEvent_shouldShowDismissView() {
+ final int offset = 100;
+ final MotionEvent stubDownEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
+ MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
+ mStubMenuView.getTranslationY());
+ final MotionEvent stubMoveEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3,
+ MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset,
+ mStubMenuView.getTranslationY() + offset);
+
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
+
+ verify(mDismissView).show();
+ }
+
+ @Test
public void dragAndDrop_shouldFlingMenuThenSpringToEdge() {
final int offset = 100;
final MotionEvent stubDownEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index 8c8d6ac..dd7ce0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -34,6 +34,7 @@
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -59,6 +60,9 @@
private WindowManager mWindowManager;
@Mock
+ private AccessibilityManager mAccessibilityManager;
+
+ @Mock
private WindowMetrics mWindowMetrics;
private MenuViewLayerController mMenuViewLayerController;
@@ -72,7 +76,8 @@
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1080, 2340));
when(mWindowMetrics.getWindowInsets()).thenReturn(stubDisplayInsets());
- mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager);
+ mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager,
+ mAccessibilityManager);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 23c6ef1..d20eeaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -23,18 +23,25 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
/** Tests for {@link MenuViewLayer}. */
@RunWith(AndroidTestingRunner.class)
@@ -43,10 +50,19 @@
public class MenuViewLayerTest extends SysuiTestCase {
private MenuViewLayer mMenuViewLayer;
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IAccessibilityFloatingMenu mFloatingMenu;
+
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
- mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager);
+ final AccessibilityManager stubAccessibilityManager = mContext.getSystemService(
+ AccessibilityManager.class);
+ mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, stubAccessibilityManager,
+ mFloatingMenu);
}
@Test
@@ -64,4 +80,11 @@
assertThat(menuView.getVisibility()).isEqualTo(GONE);
}
+
+ @Test
+ public void tiggerDismissMenuAction_hideFloatingMenu() {
+ mMenuViewLayer.mDismissMenuAction.run();
+
+ verify(mFloatingMenu).hide();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index f2ae7a1..45b8ce1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -41,10 +41,15 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
import org.junit.After
import org.junit.Rule
import org.junit.Test
@@ -54,6 +59,7 @@
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@@ -79,6 +85,15 @@
@Mock
lateinit var interactionJankMonitor: InteractionJankMonitor
+ private val biometricPromptRepository = FakePromptRepository()
+ private val credentialInteractor = FakeCredentialInteractor()
+ private val bpCredentialInteractor = BiometricPromptCredentialInteractor(
+ Dispatchers.Main.immediate,
+ biometricPromptRepository,
+ credentialInteractor
+ )
+ private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
+
private var authContainer: TestAuthContainerView? = null
@After
@@ -125,6 +140,21 @@
}
@Test
+ fun testDismissBeforeIntroEnd() {
+ val container = initializeFingerprintContainer()
+ waitForIdleSync()
+
+ // STATE_ANIMATING_IN = 1
+ container?.mContainerState = 1
+
+ container.dismissWithoutCallback(false)
+
+ // the first time is triggered by initializeFingerprintContainer()
+ // the second time was triggered by dismissWithoutCallback()
+ verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L)
+ }
+
+ @Test
fun testDismissesOnFocusLoss() {
val container = initializeFingerprintContainer()
waitForIdleSync()
@@ -450,6 +480,8 @@
userManager,
lockPatternUtils,
interactionJankMonitor,
+ { bpCredentialInteractor },
+ { credentialViewModel },
Handler(TestableLooper.get(this).looper),
FakeExecutor(FakeSystemClock())
) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 8e45067..4dd46ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -25,7 +25,9 @@
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -87,6 +89,8 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
@@ -163,6 +167,11 @@
private UdfpsLogger mUdfpsLogger;
@Mock
private InteractionJankMonitor mInteractionJankMonitor;
+ @Mock
+ private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor;
+ @Mock
+ private CredentialViewModel mCredentialViewModel;
+
@Captor
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
@Captor
@@ -236,7 +245,7 @@
2 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
1 /* maxEnrollmentsPerUser */,
- fpComponentInfo,
+ faceComponentInfo,
FaceSensorProperties.TYPE_RGB,
true /* supportsFaceDetection */,
true /* supportsSelfIllumination */,
@@ -276,8 +285,6 @@
reset(mFingerprintManager);
reset(mFaceManager);
- when(mVibratorHelper.hasVibrator()).thenReturn(true);
-
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
@@ -308,8 +315,6 @@
reset(mFingerprintManager);
reset(mFaceManager);
- when(mVibratorHelper.hasVibrator()).thenReturn(true);
-
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
@@ -343,6 +348,36 @@
}
@Test
+ public void testFaceAuthEnrollmentStatus() throws RemoteException {
+ final int userId = 0;
+
+ reset(mFaceManager);
+ mAuthController.start();
+
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+ mFaceAuthenticatorsRegisteredCaptor.capture());
+
+ mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(
+ mFaceManager.getSensorPropertiesInternal());
+ mTestableLooper.processAllMessages();
+
+ verify(mFaceManager).registerBiometricStateListener(
+ mBiometricStateCaptor.capture());
+
+ assertFalse(mAuthController.isFaceAuthEnrolled(userId));
+
+ // Enrollments changed for an unknown sensor.
+ for (BiometricStateListener listener : mBiometricStateCaptor.getAllValues()) {
+ listener.onEnrollmentsChanged(userId,
+ 2 /* sensorId */, true /* hasEnrollments */);
+ }
+ mTestableLooper.processAllMessages();
+
+ assertTrue(mAuthController.isFaceAuthEnrolled(userId));
+ }
+
+
+ @Test
public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
@@ -981,6 +1016,7 @@
fingerprintManager, faceManager, udfpsControllerFactory,
sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
mUserManager, mLockPatternUtils, mUdfpsLogger, statusBarStateController,
+ () -> mBiometricPromptCredentialInteractor, () -> mCredentialViewModel,
mInteractionJankMonitor, mHandler, mBackgroundExecutor, vibratorHelper);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 8820c16..1379a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -22,12 +22,11 @@
import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.SensorProperties
-import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.face.FaceSensorProperties
+import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.Bundle
-
import android.testing.ViewUtils
import android.view.LayoutInflater
@@ -83,26 +82,31 @@
internal fun fingerprintSensorPropertiesInternal(
ids: List<Int> = listOf(0)
): List<FingerprintSensorPropertiesInternal> {
- val componentInfo = listOf(
+ val componentInfo =
+ listOf(
ComponentInfoInternal(
- "fingerprintSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */
+ "fingerprintSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */,
+ "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */,
+ "" /* softwareVersion */
),
ComponentInfoInternal(
- "matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */
+ "matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */,
+ "" /* firmwareVersion */,
+ "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */
)
- )
+ )
return ids.map { id ->
FingerprintSensorPropertiesInternal(
- id,
- SensorProperties.STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- componentInfo,
- FingerprintSensorProperties.TYPE_REAR,
- false /* resetLockoutRequiresHardwareAuthToken */
+ id,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ FingerprintSensorProperties.TYPE_REAR,
+ false /* resetLockoutRequiresHardwareAuthToken */
)
}
}
@@ -111,28 +115,53 @@
internal fun faceSensorPropertiesInternal(
ids: List<Int> = listOf(1)
): List<FaceSensorPropertiesInternal> {
- val componentInfo = listOf(
+ val componentInfo =
+ listOf(
ComponentInfoInternal(
- "faceSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */
+ "faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */,
+ "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */,
+ "" /* softwareVersion */
),
ComponentInfoInternal(
- "matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */
+ "matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */,
+ "" /* firmwareVersion */,
+ "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */
)
- )
+ )
return ids.map { id ->
FaceSensorPropertiesInternal(
- id,
- SensorProperties.STRENGTH_STRONG,
- 2 /* maxEnrollmentsPerUser */,
- componentInfo,
- FaceSensorProperties.TYPE_RGB,
- true /* supportsFaceDetection */,
- true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresHardwareAuthToken */
+ id,
+ SensorProperties.STRENGTH_STRONG,
+ 2 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ FaceSensorProperties.TYPE_RGB,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresHardwareAuthToken */
)
}
}
+
+internal fun promptInfo(
+ title: String = "title",
+ subtitle: String = "sub",
+ description: String = "desc",
+ credentialTitle: String? = "cred title",
+ credentialSubtitle: String? = "cred sub",
+ credentialDescription: String? = "cred desc",
+ negativeButton: String = "neg",
+): PromptInfo {
+ val info = PromptInfo()
+ info.title = title
+ info.subtitle = subtitle
+ info.description = description
+ credentialTitle?.let { info.deviceCredentialTitle = it }
+ credentialSubtitle?.let { info.deviceCredentialSubtitle = it }
+ credentialDescription?.let { info.deviceCredentialDescription = it }
+ info.negativeButtonText = negativeButton
+ return info
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
new file mode 100644
index 0000000..2d5614c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -0,0 +1,81 @@
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.biometrics.PromptInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PromptRepositoryImplTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var authController: AuthController
+
+ private lateinit var repository: PromptRepositoryImpl
+
+ @Before
+ fun setup() {
+ repository = PromptRepositoryImpl(authController)
+ }
+
+ @Test
+ fun isShowing() = runBlockingTest {
+ whenever(authController.isShowing).thenReturn(true)
+
+ val values = mutableListOf<Boolean>()
+ val job = launch { repository.isShowing.toList(values) }
+ assertThat(values).containsExactly(true)
+
+ withArgCaptor<AuthController.Callback> {
+ verify(authController).addCallback(capture())
+
+ value.onBiometricPromptShown()
+ assertThat(values).containsExactly(true, true)
+
+ value.onBiometricPromptDismissed()
+ assertThat(values).containsExactly(true, true, false).inOrder()
+
+ job.cancel()
+ verify(authController).removeCallback(eq(value))
+ }
+ }
+
+ @Test
+ fun setsAndUnsetsPrompt() = runBlockingTest {
+ val kind = PromptKind.PIN
+ val uid = 8
+ val challenge = 90L
+ val promptInfo = PromptInfo()
+
+ repository.setPrompt(promptInfo, uid, challenge, kind)
+
+ assertThat(repository.kind.value).isEqualTo(kind)
+ assertThat(repository.userId.value).isEqualTo(uid)
+ assertThat(repository.challenge.value).isEqualTo(challenge)
+ assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+
+ repository.unsetPrompt()
+
+ assertThat(repository.promptInfo.value).isNull()
+ assertThat(repository.userId.value).isNull()
+ assertThat(repository.challenge.value).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
new file mode 100644
index 0000000..97d3e68
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -0,0 +1,216 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResourcesManager
+import android.content.pm.UserInfo
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
+import com.android.internal.widget.VerifyCredentialResponse
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.biometrics.promptInfo
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+private const val USER_ID = 22
+private const val OPERATION_ID = 100L
+private const val MAX_ATTEMPTS = 5
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class CredentialInteractorImplTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var devicePolicyResourcesManager: DevicePolicyResourcesManager
+
+ private val systemClock = FakeSystemClock()
+
+ private lateinit var interactor: CredentialInteractorImpl
+
+ @Before
+ fun setup() {
+ whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
+ whenever(lockPatternUtils.getMaximumFailedPasswordsForWipe(anyInt()))
+ .thenReturn(MAX_ATTEMPTS)
+ whenever(userManager.getUserInfo(eq(USER_ID))).thenReturn(UserInfo(USER_ID, "", 0))
+ whenever(devicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(eq(USER_ID)))
+ .thenReturn(USER_ID)
+
+ interactor =
+ CredentialInteractorImpl(
+ mContext,
+ lockPatternUtils,
+ userManager,
+ devicePolicyManager,
+ systemClock
+ )
+ }
+
+ @Test
+ fun testStealthMode() {
+ for (value in listOf(true, false, false, true)) {
+ whenever(lockPatternUtils.isVisiblePatternEnabled(eq(USER_ID))).thenReturn(value)
+
+ assertThat(interactor.isStealthModeActive(USER_ID)).isEqualTo(!value)
+ }
+ }
+
+ @Test
+ fun testCredentialOwner() {
+ for (value in listOf(12, 8, 4)) {
+ whenever(userManager.getCredentialOwnerProfile(eq(USER_ID))).thenReturn(value)
+
+ assertThat(interactor.getCredentialOwnerOrSelfId(USER_ID)).isEqualTo(value)
+ }
+ }
+
+ @Test fun pinCredentialWhenGood() = pinCredential(goodCredential())
+
+ @Test fun pinCredentialWhenBad() = pinCredential(badCredential())
+
+ @Test fun pinCredentialWhenBadAndThrottled() = pinCredential(badCredential(timeout = 5_000))
+
+ private fun pinCredential(result: VerifyCredentialResponse) = runTest {
+ val usedAttempts = 1
+ whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(USER_ID)))
+ .thenReturn(usedAttempts)
+ whenever(lockPatternUtils.verifyCredential(any(), eq(USER_ID), anyInt())).thenReturn(result)
+ whenever(lockPatternUtils.verifyGatekeeperPasswordHandle(anyLong(), anyLong(), eq(USER_ID)))
+ .thenReturn(result)
+ whenever(lockPatternUtils.setLockoutAttemptDeadline(anyInt(), anyInt())).thenAnswer {
+ systemClock.elapsedRealtime() + (it.arguments[1] as Int)
+ }
+
+ // wrap in an async block so the test can advance the clock if throttling credential
+ // checks prevents the method from returning
+ val statusList = mutableListOf<CredentialStatus>()
+ interactor
+ .verifyCredential(pinRequest(), LockscreenCredential.createPin("1234"))
+ .toList(statusList)
+
+ val last = statusList.removeLastOrNull()
+ if (result.isMatched) {
+ assertThat(statusList).isEmpty()
+ val successfulResult = last as? CredentialStatus.Success.Verified
+ assertThat(successfulResult).isNotNull()
+ assertThat(successfulResult!!.hat).isEqualTo(result.gatekeeperHAT)
+
+ verify(lockPatternUtils).userPresent(eq(USER_ID))
+ verify(lockPatternUtils)
+ .removeGatekeeperPasswordHandle(eq(result.gatekeeperPasswordHandle))
+ } else {
+ val failedResult = last as? CredentialStatus.Fail.Error
+ assertThat(failedResult).isNotNull()
+ assertThat(failedResult!!.remainingAttempts)
+ .isEqualTo(if (result.timeout > 0) null else MAX_ATTEMPTS - usedAttempts - 1)
+ assertThat(failedResult.urgentMessage).isNull()
+
+ if (result.timeout > 0) { // failed and throttled
+ // messages are in the throttled errors, so the final Error.error is empty
+ assertThat(failedResult.error).isEmpty()
+ assertThat(statusList).isNotEmpty()
+ assertThat(statusList.filterIsInstance(CredentialStatus.Fail.Throttled::class.java))
+ .hasSize(statusList.size)
+
+ verify(lockPatternUtils).setLockoutAttemptDeadline(eq(USER_ID), eq(result.timeout))
+ } else { // failed
+ assertThat(failedResult.error)
+ .matches(Regex("(.*)try again(.*)", RegexOption.IGNORE_CASE).toPattern())
+ assertThat(statusList).isEmpty()
+
+ verify(lockPatternUtils).reportFailedPasswordAttempt(eq(USER_ID))
+ }
+ }
+ }
+
+ @Test
+ fun pinCredentialWhenBadAndFinalAttempt() = runTest {
+ whenever(lockPatternUtils.verifyCredential(any(), eq(USER_ID), anyInt()))
+ .thenReturn(badCredential())
+ whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(USER_ID)))
+ .thenReturn(MAX_ATTEMPTS - 2)
+
+ val statusList = mutableListOf<CredentialStatus>()
+ interactor
+ .verifyCredential(pinRequest(), LockscreenCredential.createPin("1234"))
+ .toList(statusList)
+
+ val result = statusList.removeLastOrNull() as? CredentialStatus.Fail.Error
+ assertThat(result).isNotNull()
+ assertThat(result!!.remainingAttempts).isEqualTo(1)
+ assertThat(result.urgentMessage).isNotEmpty()
+ assertThat(statusList).isEmpty()
+
+ verify(lockPatternUtils).reportFailedPasswordAttempt(eq(USER_ID))
+ }
+
+ @Test
+ fun pinCredentialWhenBadAndNoMoreAttempts() = runTest {
+ whenever(lockPatternUtils.verifyCredential(any(), eq(USER_ID), anyInt()))
+ .thenReturn(badCredential())
+ whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(USER_ID)))
+ .thenReturn(MAX_ATTEMPTS - 1)
+ whenever(devicePolicyResourcesManager.getString(any(), any())).thenReturn("wipe")
+
+ val statusList = mutableListOf<CredentialStatus>()
+ interactor
+ .verifyCredential(pinRequest(), LockscreenCredential.createPin("1234"))
+ .toList(statusList)
+
+ val result = statusList.removeLastOrNull() as? CredentialStatus.Fail.Error
+ assertThat(result).isNotNull()
+ assertThat(result!!.remainingAttempts).isEqualTo(0)
+ assertThat(result.urgentMessage).isNotEmpty()
+ assertThat(statusList).isEmpty()
+
+ verify(lockPatternUtils).reportFailedPasswordAttempt(eq(USER_ID))
+ }
+}
+
+private fun pinRequest(): BiometricPromptRequest.Credential.Pin =
+ BiometricPromptRequest.Credential.Pin(
+ promptInfo(),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ )
+
+private fun goodCredential(
+ passwordHandle: Long = 90,
+ hat: ByteArray = ByteArray(69),
+): VerifyCredentialResponse =
+ VerifyCredentialResponse.Builder()
+ .setGatekeeperPasswordHandle(passwordHandle)
+ .setGatekeeperHAT(hat)
+ .build()
+
+private fun badCredential(timeout: Int = 0): VerifyCredentialResponse =
+ if (timeout > 0) {
+ VerifyCredentialResponse.fromTimeout(timeout)
+ } else {
+ VerifyCredentialResponse.fromError()
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
new file mode 100644
index 0000000..dbcbf41
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -0,0 +1,270 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import android.hardware.biometrics.PromptInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.biometrics.promptInfo
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.junit.MockitoJUnit
+
+private const val USER_ID = 22
+private const val OPERATION_ID = 100L
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class PromptCredentialInteractorTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val biometricPromptRepository = FakePromptRepository()
+ private val credentialInteractor = FakeCredentialInteractor()
+
+ private lateinit var interactor: BiometricPromptCredentialInteractor
+
+ @Before
+ fun setup() {
+ interactor =
+ BiometricPromptCredentialInteractor(
+ dispatcher,
+ biometricPromptRepository,
+ credentialInteractor
+ )
+ }
+
+ @Test
+ fun testIsShowing() =
+ runTest(dispatcher) {
+ var showing = false
+ val job = launch { interactor.isShowing.collect { showing = it } }
+
+ biometricPromptRepository.setIsShowing(false)
+ assertThat(showing).isFalse()
+
+ biometricPromptRepository.setIsShowing(true)
+ assertThat(showing).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun testShowError() =
+ runTest(dispatcher) {
+ var error: CredentialStatus.Fail? = null
+ val job = launch { interactor.verificationError.collect { error = it } }
+
+ for (msg in listOf("once", "again")) {
+ interactor.setVerificationError(error(msg))
+ assertThat(error).isEqualTo(error(msg))
+ }
+
+ interactor.resetVerificationError()
+ assertThat(error).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun nullWhenNoPromptInfo() =
+ runTest(dispatcher) {
+ var prompt: BiometricPromptRequest? = null
+ val job = launch { interactor.prompt.collect { prompt = it } }
+
+ assertThat(prompt).isNull()
+
+ job.cancel()
+ }
+
+ @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PIN)
+
+ @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PASSWORD)
+
+ @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PATTERN)
+
+ private fun useCredentialForPrompt(kind: Int) =
+ runTest(dispatcher) {
+ val isStealth = false
+ credentialInteractor.stealthMode = isStealth
+
+ var prompt: BiometricPromptRequest? = null
+ val job = launch { interactor.prompt.collect { prompt = it } }
+
+ val title = "what a prompt"
+ val subtitle = "s"
+ val description = "something to see"
+
+ interactor.useCredentialsForAuthentication(
+ PromptInfo().also {
+ it.title = title
+ it.description = description
+ it.subtitle = subtitle
+ },
+ kind = kind,
+ userId = USER_ID,
+ challenge = OPERATION_ID
+ )
+
+ val p = prompt as? BiometricPromptRequest.Credential
+ assertThat(p).isNotNull()
+ assertThat(p!!.title).isEqualTo(title)
+ assertThat(p.subtitle).isEqualTo(subtitle)
+ assertThat(p.description).isEqualTo(description)
+ assertThat(p.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
+ assertThat(p.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
+ assertThat(p)
+ .isInstanceOf(
+ when (kind) {
+ Utils.CREDENTIAL_PIN -> BiometricPromptRequest.Credential.Pin::class.java
+ Utils.CREDENTIAL_PASSWORD ->
+ BiometricPromptRequest.Credential.Password::class.java
+ Utils.CREDENTIAL_PATTERN ->
+ BiometricPromptRequest.Credential.Pattern::class.java
+ else -> throw Exception("wrong kind")
+ }
+ )
+ if (p is BiometricPromptRequest.Credential.Pattern) {
+ assertThat(p.stealthMode).isEqualTo(isStealth)
+ }
+
+ interactor.resetPrompt()
+
+ assertThat(prompt).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredential() =
+ runTest(dispatcher) {
+ val hat = ByteArray(4)
+ credentialInteractor.verifyCredentialResponse = { _ -> flowOf(verified(hat)) }
+
+ val errors = mutableListOf<CredentialStatus.Fail?>()
+ val job = launch { interactor.verificationError.toList(errors) }
+
+ val checked =
+ interactor.checkCredential(pinRequest(), text = "1234")
+ as? CredentialStatus.Success.Verified
+
+ assertThat(checked).isNotNull()
+ assertThat(checked!!.hat).isSameInstanceAs(hat)
+ assertThat(errors.map { it?.error }).containsExactly(null)
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredentialWhenBad() =
+ runTest(dispatcher) {
+ val errorMessage = "bad"
+ val remainingAttempts = 12
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flowOf(error(errorMessage, remainingAttempts))
+ }
+
+ val errors = mutableListOf<CredentialStatus.Fail?>()
+ val job = launch { interactor.verificationError.toList(errors) }
+
+ val checked =
+ interactor.checkCredential(pinRequest(), text = "1234")
+ as? CredentialStatus.Fail.Error
+
+ assertThat(checked).isNotNull()
+ assertThat(checked!!.remainingAttempts).isEqualTo(remainingAttempts)
+ assertThat(checked.urgentMessage).isNull()
+ assertThat(errors.map { it?.error }).containsExactly(null, errorMessage).inOrder()
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredentialWhenBadAndUrgentMessage() =
+ runTest(dispatcher) {
+ val error = "not so bad"
+ val urgentMessage = "really bad"
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flowOf(error(error, 10, urgentMessage))
+ }
+
+ val errors = mutableListOf<CredentialStatus.Fail?>()
+ val job = launch { interactor.verificationError.toList(errors) }
+
+ val checked =
+ interactor.checkCredential(pinRequest(), text = "1234")
+ as? CredentialStatus.Fail.Error
+
+ assertThat(checked).isNotNull()
+ assertThat(checked!!.urgentMessage).isEqualTo(urgentMessage)
+ assertThat(errors.map { it?.error }).containsExactly(null, error).inOrder()
+ assertThat(errors.last() as? CredentialStatus.Fail.Error)
+ .isEqualTo(error(error, 10, urgentMessage))
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredentialWhenBadAndThrottled() =
+ runTest(dispatcher) {
+ val remainingAttempts = 3
+ val error = ":("
+ val urgentMessage = ":D"
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flow {
+ for (i in 1..3) {
+ emit(throttled("$i"))
+ delay(100)
+ }
+ emit(error(error, remainingAttempts, urgentMessage))
+ }
+ }
+ val errors = mutableListOf<CredentialStatus.Fail?>()
+ val job = launch { interactor.verificationError.toList(errors) }
+
+ val checked =
+ interactor.checkCredential(pinRequest(), text = "1234")
+ as? CredentialStatus.Fail.Error
+
+ assertThat(checked).isNotNull()
+ assertThat(checked!!.remainingAttempts).isEqualTo(remainingAttempts)
+ assertThat(checked.urgentMessage).isEqualTo(urgentMessage)
+ assertThat(errors.map { it?.error })
+ .containsExactly(null, "1", "2", "3", error)
+ .inOrder()
+
+ job.cancel()
+ }
+}
+
+private fun pinRequest(): BiometricPromptRequest.Credential.Pin =
+ BiometricPromptRequest.Credential.Pin(
+ promptInfo(),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ )
+
+private fun verified(hat: ByteArray) = CredentialStatus.Success.Verified(hat)
+
+private fun throttled(error: String) = CredentialStatus.Fail.Throttled(error)
+
+private fun error(error: String? = null, remaining: Int? = null, urgentMessage: String? = null) =
+ CredentialStatus.Fail.Error(error, remaining, urgentMessage)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
new file mode 100644
index 0000000..2eeff9f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -0,0 +1,92 @@
+package com.android.systemui.biometrics.domain.model
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.biometrics.promptInfo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private const val USER_ID = 2
+private const val OPERATION_ID = 8L
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BiometricPromptRequestTest {
+
+ @Test
+ fun biometricRequestFromPromptInfo() {
+ val title = "what"
+ val subtitle = "a"
+ val description = "request"
+
+ val request =
+ BiometricPromptRequest.Biometric(
+ promptInfo(title = title, subtitle = subtitle, description = description),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ )
+
+ assertThat(request.title).isEqualTo(title)
+ assertThat(request.subtitle).isEqualTo(subtitle)
+ assertThat(request.description).isEqualTo(description)
+ assertThat(request.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
+ assertThat(request.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
+ }
+
+ @Test
+ fun credentialRequestFromPromptInfo() {
+ val title = "what"
+ val subtitle = "a"
+ val description = "request"
+ val stealth = true
+
+ val toCheck =
+ listOf(
+ BiometricPromptRequest.Credential.Pin(
+ promptInfo(
+ title = title,
+ subtitle = subtitle,
+ description = description,
+ credentialTitle = null,
+ credentialSubtitle = null,
+ credentialDescription = null
+ ),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ ),
+ BiometricPromptRequest.Credential.Password(
+ promptInfo(
+ credentialTitle = title,
+ credentialSubtitle = subtitle,
+ credentialDescription = description
+ ),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ ),
+ BiometricPromptRequest.Credential.Pattern(
+ promptInfo(
+ subtitle = subtitle,
+ description = description,
+ credentialTitle = title,
+ credentialSubtitle = null,
+ credentialDescription = null
+ ),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID),
+ stealth
+ )
+ )
+
+ for (request in toCheck) {
+ assertThat(request.title).isEqualTo(title)
+ assertThat(request.subtitle).isEqualTo(subtitle)
+ assertThat(request.description).isEqualTo(description)
+ assertThat(request.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
+ assertThat(request.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
+ if (request is BiometricPromptRequest.Credential.Pattern) {
+ assertThat(request.stealthMode).isEqualTo(stealth)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
new file mode 100644
index 0000000..d73cdfc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
@@ -0,0 +1,181 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.CredentialStatus
+import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
+import com.android.systemui.biometrics.promptInfo
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private const val USER_ID = 9
+private const val OPERATION_ID = 10L
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class CredentialViewModelTest : SysuiTestCase() {
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val promptRepository = FakePromptRepository()
+ private val credentialInteractor = FakeCredentialInteractor()
+
+ private lateinit var viewModel: CredentialViewModel
+
+ @Before
+ fun setup() {
+ viewModel =
+ CredentialViewModel(
+ mContext,
+ BiometricPromptCredentialInteractor(
+ dispatcher,
+ promptRepository,
+ credentialInteractor
+ )
+ )
+ }
+
+ @Test fun setsPinInputFlags() = setsInputFlags(PromptKind.PIN, expectFlags = true)
+ @Test fun setsPasswordInputFlags() = setsInputFlags(PromptKind.PASSWORD, expectFlags = false)
+ @Test fun setsPatternInputFlags() = setsInputFlags(PromptKind.PATTERN, expectFlags = false)
+
+ private fun setsInputFlags(type: PromptKind, expectFlags: Boolean) =
+ runTestWithKind(type) {
+ var flags: Int? = null
+ val job = launch { viewModel.inputFlags.collect { flags = it } }
+
+ if (expectFlags) {
+ assertThat(flags).isNotNull()
+ } else {
+ assertThat(flags).isNull()
+ }
+ job.cancel()
+ }
+
+ @Test fun isStealthIgnoredByPin() = isStealthMode(PromptKind.PIN, expectStealth = false)
+ @Test
+ fun isStealthIgnoredByPassword() = isStealthMode(PromptKind.PASSWORD, expectStealth = false)
+ @Test fun isStealthUsedByPattern() = isStealthMode(PromptKind.PATTERN, expectStealth = true)
+
+ private fun isStealthMode(type: PromptKind, expectStealth: Boolean) =
+ runTestWithKind(type, init = { credentialInteractor.stealthMode = true }) {
+ var stealth: Boolean? = null
+ val job = launch { viewModel.stealthMode.collect { stealth = it } }
+
+ assertThat(stealth).isEqualTo(expectStealth)
+
+ job.cancel()
+ }
+
+ @Test
+ fun animatesContents() = runTestWithKind {
+ val expected = arrayOf(true, false, true)
+ val animate = mutableListOf<Boolean>()
+ val job = launch { viewModel.animateContents.toList(animate) }
+
+ for (value in expected) {
+ viewModel.setAnimateContents(value)
+ viewModel.setAnimateContents(value)
+ }
+ assertThat(animate).containsExactly(*expected).inOrder()
+
+ job.cancel()
+ }
+
+ @Test
+ fun showAndClearErrors() = runTestWithKind {
+ var error = ""
+ val job = launch { viewModel.errorMessage.collect { error = it } }
+ assertThat(error).isEmpty()
+
+ viewModel.showPatternTooShortError()
+ assertThat(error).isNotEmpty()
+
+ viewModel.resetErrorMessage()
+ assertThat(error).isEmpty()
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredential() = runTestWithKind {
+ val hat = ByteArray(2)
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flowOf(CredentialStatus.Success.Verified(hat))
+ }
+
+ val attestations = mutableListOf<ByteArray?>()
+ val remainingAttempts = mutableListOf<RemainingAttempts?>()
+ var header: HeaderViewModel? = null
+ val job = launch {
+ launch { viewModel.validatedAttestation.toList(attestations) }
+ launch { viewModel.remainingAttempts.toList(remainingAttempts) }
+ launch { viewModel.header.collect { header = it } }
+ }
+ assertThat(header).isNotNull()
+
+ viewModel.checkCredential("p", header!!)
+
+ val attestation = attestations.removeLastOrNull()
+ assertThat(attestation).isSameInstanceAs(hat)
+ assertThat(attestations).isEmpty()
+ assertThat(remainingAttempts).containsExactly(RemainingAttempts())
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredentialWhenBad() = runTestWithKind {
+ val remaining = 2
+ val urgentError = "wow"
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flowOf(CredentialStatus.Fail.Error("error", remaining, urgentError))
+ }
+
+ val attestations = mutableListOf<ByteArray?>()
+ val remainingAttempts = mutableListOf<RemainingAttempts?>()
+ var header: HeaderViewModel? = null
+ val job = launch {
+ launch { viewModel.validatedAttestation.toList(attestations) }
+ launch { viewModel.remainingAttempts.toList(remainingAttempts) }
+ launch { viewModel.header.collect { header = it } }
+ }
+ assertThat(header).isNotNull()
+
+ viewModel.checkCredential("1111", header!!)
+
+ assertThat(attestations).containsExactly(null)
+
+ val attemptInfo = remainingAttempts.removeLastOrNull()
+ assertThat(attemptInfo).isNotNull()
+ assertThat(attemptInfo!!.remaining).isEqualTo(remaining)
+ assertThat(attemptInfo.message).isEqualTo(urgentError)
+ assertThat(remainingAttempts).containsExactly(RemainingAttempts()) // initial value
+
+ job.cancel()
+ }
+
+ private fun runTestWithKind(
+ kind: PromptKind = PromptKind.PIN,
+ init: () -> Unit = {},
+ block: suspend TestScope.() -> Unit,
+ ) =
+ runTest(dispatcher) {
+ init()
+ promptRepository.setPrompt(promptInfo(), USER_ID, OPERATION_ID, kind)
+ block()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
index e099c92..ea16cb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
@@ -20,6 +20,7 @@
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_MEDIA_ENTRY;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_SMARTSPACE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
@@ -63,6 +64,8 @@
.isEqualTo(COMPLICATION_TYPE_HOME_CONTROLS);
assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_SMARTSPACE))
.isEqualTo(COMPLICATION_TYPE_SMARTSPACE);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_MEDIA_ENTRY))
+ .isEqualTo(COMPLICATION_TYPE_MEDIA_ENTRY);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
index 50f27ea..0295030 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
@@ -16,8 +16,11 @@
package com.android.systemui.dreams.complication;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_MEDIA_ENTRY;
import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -32,6 +35,7 @@
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.controls.ui.MediaCarouselController;
import com.android.systemui.media.dream.MediaDreamComplication;
@@ -51,6 +55,9 @@
@TestableLooper.RunWithLooper
public class DreamMediaEntryComplicationTest extends SysuiTestCase {
@Mock
+ private DreamMediaEntryComplicationComponent.Factory mComponentFactory;
+
+ @Mock
private View mView;
@Mock
@@ -89,6 +96,14 @@
when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(false);
}
+ @Test
+ public void testGetRequiredTypeAvailability() {
+ final DreamMediaEntryComplication complication =
+ new DreamMediaEntryComplication(mComponentFactory);
+ assertThat(complication.getRequiredTypeAvailability()).isEqualTo(
+ COMPLICATION_TYPE_MEDIA_ENTRY);
+ }
+
/**
* Ensures clicking media entry chip adds/removes media complication.
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 20a82c6..4b3b70e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -88,6 +88,7 @@
flagMap,
restarter
)
+ mFeatureFlagsDebug.init()
verify(flagManager).onSettingsChangedAction = any()
broadcastReceiver = withArgCaptor {
verify(mockContext).registerReceiver(capture(), any(), nullable(), nullable(),
@@ -255,11 +256,11 @@
broadcastReceiver.onReceive(mockContext, Intent())
broadcastReceiver.onReceive(mockContext, Intent("invalid action"))
broadcastReceiver.onReceive(mockContext, Intent(FlagManager.ACTION_SET_FLAG))
- setByBroadcast(0, false) // unknown id does nothing
- setByBroadcast(1, "string") // wrong type does nothing
- setByBroadcast(2, 123) // wrong type does nothing
- setByBroadcast(3, false) // wrong type does nothing
- setByBroadcast(4, 123) // wrong type does nothing
+ setByBroadcast(0, false) // unknown id does nothing
+ setByBroadcast(1, "string") // wrong type does nothing
+ setByBroadcast(2, 123) // wrong type does nothing
+ setByBroadcast(3, false) // wrong type does nothing
+ setByBroadcast(4, 123) // wrong type does nothing
verifyNoMoreInteractions(flagManager, secureSettings)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index 575c142..b2dd60c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -38,8 +38,9 @@
@Mock private lateinit var mResources: Resources
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper
+ @Mock private lateinit var restarter: Restarter
+ private val flagMap = mutableMapOf<Int, Flag<*>>()
private val serverFlagReader = ServerFlagReaderFake()
-
private val deviceConfig = DeviceConfigProxyFake()
@Before
@@ -49,7 +50,9 @@
mResources,
mSystemProperties,
deviceConfig,
- serverFlagReader)
+ serverFlagReader,
+ flagMap,
+ restarter)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
new file mode 100644
index 0000000..6f5f460
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.DeviceConfigProxyFake
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ServerFlagReaderImplTest : SysuiTestCase() {
+
+ private val NAMESPACE = "test"
+
+ @Mock private lateinit var changeListener: ServerFlagReader.ChangeListener
+
+ private lateinit var serverFlagReader: ServerFlagReaderImpl
+ private val deviceConfig = DeviceConfigProxyFake()
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor)
+ }
+
+ @Test
+ fun testChange_alertsListener() {
+ val flag = ReleasedFlag(1)
+ serverFlagReader.listenForChanges(listOf(flag), changeListener)
+
+ deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
+ executor.runAllReady()
+
+ verify(changeListener).onChange()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 4c986bf..2c3ddd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -60,6 +60,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -112,6 +113,8 @@
private FalsingCollectorFake mFalsingCollector;
+ private @Mock CentralSurfaces mCentralSurfaces;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -258,6 +261,26 @@
verify(mKeyguardStateController).notifyKeyguardGoingAway(false);
}
+ @Test
+ public void testUpdateIsKeyguardAfterOccludeAnimationEnds() {
+ mViewMediator.mOccludeAnimationController.onLaunchAnimationEnd(
+ false /* isExpandingFullyAbove */);
+
+ // Since the updateIsKeyguard call is delayed during the animation, ensure it's called once
+ // it ends.
+ verify(mCentralSurfaces).updateIsKeyguard();
+ }
+
+ @Test
+ public void testUpdateIsKeyguardAfterOccludeAnimationIsCancelled() {
+ mViewMediator.mOccludeAnimationController.onLaunchAnimationCancelled(
+ null /* newKeyguardOccludedState */);
+
+ // Since the updateIsKeyguard call is delayed during the animation, ensure it's called if
+ // it's cancelled.
+ verify(mCentralSurfaces).updateIsKeyguard();
+ }
+
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
@@ -287,5 +310,7 @@
mNotificationShadeWindowControllerLazy,
() -> mActivityLaunchAnimator);
mViewMediator.start();
+
+ mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
deleted file mode 100644
index 27a5190..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ /dev/null
@@ -1,478 +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.systemui.keyguard;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.keyguard.LockIconView.ICON_LOCK;
-import static com.android.keyguard.LockIconView.ICON_UNLOCK;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.AnimatedStateListDrawable;
-import android.hardware.biometrics.BiometricSourceType;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.Pair;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.KeyguardViewController;
-import com.android.keyguard.LockIconView;
-import com.android.keyguard.LockIconViewController;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.AuthRippleController;
-import com.android.systemui.doze.util.BurnInHelperKt;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class LockIconViewControllerTest extends SysuiTestCase {
- private static final String UNLOCKED_LABEL = "unlocked";
- private static final int PADDING = 10;
-
- private MockitoSession mStaticMockSession;
-
- private @Mock LockIconView mLockIconView;
- private @Mock AnimatedStateListDrawable mIconDrawable;
- private @Mock Context mContext;
- private @Mock Resources mResources;
- private @Mock(answer = Answers.RETURNS_DEEP_STUBS) WindowManager mWindowManager;
- private @Mock StatusBarStateController mStatusBarStateController;
- private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private @Mock KeyguardViewController mKeyguardViewController;
- private @Mock KeyguardStateController mKeyguardStateController;
- private @Mock FalsingManager mFalsingManager;
- private @Mock AuthController mAuthController;
- private @Mock DumpManager mDumpManager;
- private @Mock AccessibilityManager mAccessibilityManager;
- private @Mock ConfigurationController mConfigurationController;
- private @Mock VibratorHelper mVibrator;
- private @Mock AuthRippleController mAuthRippleController;
- private FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
-
- private LockIconViewController mLockIconViewController;
-
- // Capture listeners so that they can be used to send events
- @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
- private View.OnAttachStateChangeListener mAttachListener;
-
- @Captor private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCaptor =
- ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
- private KeyguardStateController.Callback mKeyguardStateCallback;
-
- @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor =
- ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
- private StatusBarStateController.StateListener mStatusBarStateListener;
-
- @Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
- private AuthController.Callback mAuthControllerCallback;
-
- @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback>
- mKeyguardUpdateMonitorCallbackCaptor =
- ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
- private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
-
- @Captor private ArgumentCaptor<Point> mPointCaptor;
-
- @Before
- public void setUp() throws Exception {
- mStaticMockSession = mockitoSession()
- .mockStatic(BurnInHelperKt.class)
- .strictness(Strictness.LENIENT)
- .startMocking();
- MockitoAnnotations.initMocks(this);
-
- setupLockIconViewMocks();
- when(mContext.getResources()).thenReturn(mResources);
- when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
- Rect windowBounds = new Rect(0, 0, 800, 1200);
- when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds);
- when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
- when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
- when(mResources.getDimensionPixelSize(R.dimen.lock_icon_padding)).thenReturn(PADDING);
- when(mAuthController.getScaleFactor()).thenReturn(1f);
-
- when(mKeyguardStateController.isShowing()).thenReturn(true);
- when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-
- mLockIconViewController = new LockIconViewController(
- mLockIconView,
- mStatusBarStateController,
- mKeyguardUpdateMonitor,
- mKeyguardViewController,
- mKeyguardStateController,
- mFalsingManager,
- mAuthController,
- mDumpManager,
- mAccessibilityManager,
- mConfigurationController,
- mDelayableExecutor,
- mVibrator,
- mAuthRippleController,
- mResources
- );
- }
-
- @After
- public void tearDown() {
- mStaticMockSession.finishMocking();
- }
-
- @Test
- public void testUpdateFingerprintLocationOnInit() {
- // GIVEN fp sensor location is available pre-attached
- Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
-
- // WHEN lock icon view controller is initialized and attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN lock icon view location is updated to the udfps location with UDFPS radius
- verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
- eq(PADDING));
- }
-
- @Test
- public void testUpdatePaddingBasedOnResolutionScale() {
- // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5
- Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
- when(mAuthController.getScaleFactor()).thenReturn(5f);
-
- // WHEN lock icon view controller is initialized and attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN lock icon view location is updated with the scaled radius
- verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
- eq(PADDING * 5));
- }
-
- @Test
- public void testUpdateLockIconLocationOnAuthenticatorsRegistered() {
- // GIVEN fp sensor location is not available pre-init
- when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
- when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- resetLockIconView(); // reset any method call counts for when we verify method calls later
-
- // GIVEN fp sensor location is available post-attached
- captureAuthControllerCallback();
- Pair<Float, Point> udfps = setupUdfps();
-
- // WHEN all authenticators are registered
- mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
- mDelayableExecutor.runAllReady();
-
- // THEN lock icon view location is updated with the same coordinates as auth controller vals
- verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
- eq(PADDING));
- }
-
- @Test
- public void testUpdateLockIconLocationOnUdfpsLocationChanged() {
- // GIVEN fp sensor location is not available pre-init
- when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
- when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- resetLockIconView(); // reset any method call counts for when we verify method calls later
-
- // GIVEN fp sensor location is available post-attached
- captureAuthControllerCallback();
- Pair<Float, Point> udfps = setupUdfps();
-
- // WHEN udfps location changes
- mAuthControllerCallback.onUdfpsLocationChanged();
- mDelayableExecutor.runAllReady();
-
- // THEN lock icon view location is updated with the same coordinates as auth controller vals
- verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
- eq(PADDING));
- }
-
- @Test
- public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() {
- // GIVEN Udpfs sensor location is available
- setupUdfps();
-
- mLockIconViewController.init();
- captureAttachListener();
-
- // WHEN the view is attached
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN the lock icon view background should be enabled
- verify(mLockIconView).setUseBackground(true);
- }
-
- @Test
- public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() {
- // GIVEN Udfps sensor location is not supported
- when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
-
- mLockIconViewController.init();
- captureAttachListener();
-
- // WHEN the view is attached
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN the lock icon view background should be disabled
- verify(mLockIconView).setUseBackground(false);
- }
-
- @Test
- public void testUnlockIconShows_biometricUnlockedTrue() {
- // GIVEN UDFPS sensor location is available
- setupUdfps();
-
- // GIVEN lock icon controller is initialized and view is attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureKeyguardUpdateMonitorCallback();
-
- // GIVEN user has unlocked with a biometric auth (ie: face auth)
- when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
- reset(mLockIconView);
-
- // WHEN face auth's biometric running state changes
- mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
- BiometricSourceType.FACE);
-
- // THEN the unlock icon is shown
- verify(mLockIconView).setContentDescription(UNLOCKED_LABEL);
- }
-
- @Test
- public void testLockIconStartState() {
- // GIVEN lock icon state
- setupShowLockIcon();
-
- // WHEN lock icon controller is initialized
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN the lock icon should show
- verify(mLockIconView).updateIcon(ICON_LOCK, false);
- }
-
- @Test
- public void testLockIcon_updateToUnlock() {
- // GIVEN starting state for the lock icon
- setupShowLockIcon();
-
- // GIVEN lock icon controller is initialized and view is attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureKeyguardStateCallback();
- reset(mLockIconView);
-
- // WHEN the unlocked state changes to canDismissLockScreen=true
- when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
- mKeyguardStateCallback.onUnlockedChanged();
-
- // THEN the unlock should show
- verify(mLockIconView).updateIcon(ICON_UNLOCK, false);
- }
-
- @Test
- public void testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() {
- // GIVEN udfps not enrolled
- setupUdfps();
- when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false);
-
- // GIVEN starting state for the lock icon
- setupShowLockIcon();
-
- // GIVEN lock icon controller is initialized and view is attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureStatusBarStateListener();
- reset(mLockIconView);
-
- // WHEN the dozing state changes
- mStatusBarStateListener.onDozingChanged(true /* isDozing */);
-
- // THEN the icon is cleared
- verify(mLockIconView).clearIcon();
- }
-
- @Test
- public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() {
- // GIVEN udfps enrolled
- setupUdfps();
- when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
-
- // GIVEN starting state for the lock icon
- setupShowLockIcon();
-
- // GIVEN lock icon controller is initialized and view is attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureStatusBarStateListener();
- reset(mLockIconView);
-
- // WHEN the dozing state changes
- mStatusBarStateListener.onDozingChanged(true /* isDozing */);
-
- // THEN the AOD lock icon should show
- verify(mLockIconView).updateIcon(ICON_LOCK, true);
- }
-
- @Test
- public void testBurnInOffsetsUpdated_onDozeAmountChanged() {
- // GIVEN udfps enrolled
- setupUdfps();
- when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
-
- // GIVEN burn-in offset = 5
- int burnInOffset = 5;
- when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset);
-
- // GIVEN starting state for the lock icon (keyguard)
- setupShowLockIcon();
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureStatusBarStateListener();
- reset(mLockIconView);
-
- // WHEN dozing updates
- mStatusBarStateListener.onDozingChanged(true /* isDozing */);
- mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
-
- // THEN the view's translation is updated to use the AoD burn-in offsets
- verify(mLockIconView).setTranslationY(burnInOffset);
- verify(mLockIconView).setTranslationX(burnInOffset);
- reset(mLockIconView);
-
- // WHEN the device is no longer dozing
- mStatusBarStateListener.onDozingChanged(false /* isDozing */);
- mStatusBarStateListener.onDozeAmountChanged(0f, 0f);
-
- // THEN the view is updated to NO translation (no burn-in offsets anymore)
- verify(mLockIconView).setTranslationY(0);
- verify(mLockIconView).setTranslationX(0);
-
- }
- private Pair<Float, Point> setupUdfps() {
- when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
- final Point udfpsLocation = new Point(50, 75);
- final float radius = 33f;
- when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation);
- when(mAuthController.getUdfpsRadius()).thenReturn(radius);
-
- return new Pair(radius, udfpsLocation);
- }
-
- private void setupShowLockIcon() {
- when(mKeyguardStateController.isShowing()).thenReturn(true);
- when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
- }
-
- private void captureAuthControllerCallback() {
- verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
- mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
- }
-
- private void captureAttachListener() {
- verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture());
- mAttachListener = mAttachCaptor.getValue();
- }
-
- private void captureKeyguardStateCallback() {
- verify(mKeyguardStateController).addCallback(mKeyguardStateCaptor.capture());
- mKeyguardStateCallback = mKeyguardStateCaptor.getValue();
- }
-
- private void captureStatusBarStateListener() {
- verify(mStatusBarStateController).addCallback(mStatusBarStateCaptor.capture());
- mStatusBarStateListener = mStatusBarStateCaptor.getValue();
- }
-
- private void captureKeyguardUpdateMonitorCallback() {
- verify(mKeyguardUpdateMonitor).registerCallback(
- mKeyguardUpdateMonitorCallbackCaptor.capture());
- mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
- }
-
- private void setupLockIconViewMocks() {
- when(mLockIconView.getResources()).thenReturn(mResources);
- when(mLockIconView.getContext()).thenReturn(mContext);
- }
-
- private void resetLockIconView() {
- reset(mLockIconView);
- setupLockIconViewMocks();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
similarity index 87%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index e99c139..ce11008 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -15,10 +15,10 @@
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import com.android.systemui.animation.Expandable
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.yield
@@ -29,7 +29,9 @@
* This class is abstract to force tests to provide extensions of it as the system that references
* these configs uses each implementation's class type to refer to them.
*/
-abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
+abstract class FakeKeyguardQuickAffordanceConfig(
+ override val key: String,
+) : KeyguardQuickAffordanceConfig {
var onClickedResult: OnClickedResult = OnClickedResult.Handled
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
similarity index 88%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index 9a91ea91..b120303 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
similarity index 85%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index a809f05..ce8d36d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -23,7 +23,7 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
similarity index 86%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 329c4db..9346440 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -1,26 +1,26 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.content.Intent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 98dc4c4..ae9e3c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsResponse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index b4d5464..114cf19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -25,11 +25,12 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -211,7 +212,11 @@
MockitoAnnotations.initMocks(this)
whenever(expandable.activityLaunchController()).thenReturn(animationController)
- homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+ homeControls =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ ) {}
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
@@ -224,8 +229,14 @@
),
KeyguardQuickAffordancePosition.BOTTOM_END to
listOf(
- object : FakeKeyguardQuickAffordanceConfig() {},
- object : FakeKeyguardQuickAffordanceConfig() {},
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ ) {},
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ ) {},
),
),
),
@@ -260,7 +271,7 @@
}
underTest.onQuickAffordanceClicked(
- configKey = homeControls::class,
+ configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
expandable = expandable,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 65fd6e5..1a1ee8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -22,12 +22,13 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
@@ -69,9 +70,21 @@
repository = FakeKeyguardRepository()
repository.setKeyguardShowing(true)
- homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
- quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
- qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
+ homeControls =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ ) {}
+ quickAccessWallet =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ ) {}
+ qrCodeScanner =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ ) {}
underTest =
KeyguardQuickAffordanceInteractor(
@@ -99,7 +112,7 @@
@Test
fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
- val configKey = homeControls::class
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
@@ -130,7 +143,7 @@
@Test
fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
- val configKey = quickAccessWallet::class
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
quickAccessWallet.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
index e68c43f..13e2768 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.domain.quickaffordance
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import kotlin.reflect.KClass
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
class FakeKeyguardQuickAffordanceRegistry(
@@ -33,11 +33,8 @@
}
override fun get(
- configClass: KClass<out FakeKeyguardQuickAffordanceConfig>
+ key: String,
): FakeKeyguardQuickAffordanceConfig {
- return configsByPosition.values
- .flatten()
- .associateBy { config -> config::class }
- .getValue(configClass)
+ return configsByPosition.values.flatten().associateBy { config -> config.key }.getValue(key)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index d674c89..f9be067 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -23,14 +23,15 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
@@ -40,7 +41,6 @@
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
-import kotlin.reflect.KClass
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
@@ -81,9 +81,21 @@
whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
.thenReturn(RETURNED_BURN_IN_OFFSET)
- homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
- quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
- qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ homeControlsQuickAffordanceConfig =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ ) {}
+ quickAccessWalletAffordanceConfig =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ ) {}
+ qrCodeScannerAffordanceConfig =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ ) {}
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
@@ -489,7 +501,7 @@
private suspend fun setUpQuickAffordanceModel(
position: KeyguardQuickAffordancePosition,
testConfig: TestConfig,
- ): KClass<out FakeKeyguardQuickAffordanceConfig> {
+ ): String {
val config =
when (position) {
KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
@@ -518,13 +530,13 @@
KeyguardQuickAffordanceConfig.State.Hidden
}
config.setState(state)
- return config::class
+ return config.key
}
private fun assertQuickAffordanceViewModel(
viewModel: KeyguardQuickAffordanceViewModel?,
testConfig: TestConfig,
- configKey: KClass<out FakeKeyguardQuickAffordanceConfig>,
+ configKey: String,
) {
checkNotNull(viewModel)
assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
new file mode 100644
index 0000000..f20c6a2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.notetask
+
+import android.app.KeyguardManager
+import android.content.Context
+import android.content.Intent
+import android.os.UserManager
+import android.test.suitebuilder.annotation.SmallTest
+import android.view.KeyEvent
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.floating.FloatingTasks
+import java.util.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskController].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskControllerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskControllerTest : SysuiTestCase() {
+
+ private val notesIntent = Intent(NOTES_ACTION)
+
+ @Mock lateinit var context: Context
+ @Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
+ @Mock lateinit var floatingTasks: FloatingTasks
+ @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
+ @Mock lateinit var keyguardManager: KeyguardManager
+ @Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager>
+ @Mock lateinit var optionalUserManager: Optional<UserManager>
+ @Mock lateinit var userManager: UserManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
+ whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
+ whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
+ whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
+ whenever(userManager.isUserUnlocked).thenReturn(true)
+ }
+
+ private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController {
+ return NoteTaskController(
+ context = context,
+ intentResolver = noteTaskIntentResolver,
+ optionalFloatingTasks = optionalFloatingTasks,
+ optionalKeyguardManager = optionalKeyguardManager,
+ optionalUserManager = optionalUserManager,
+ isEnabled = isEnabled,
+ )
+ }
+
+ @Test
+ fun handleSystemKey_keyguardIsLocked_shouldStartActivity() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_keyguardIsUnlocked_shouldStartFloatingTask() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(floatingTasks).showOrSetStashed(notesIntent)
+ verify(context, never()).startActivity(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_floatingTasksIsNull_shouldDoNothing() {
+ whenever(optionalFloatingTasks.orElse(null)).thenReturn(null)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_keyguardManagerIsNull_shouldDoNothing() {
+ whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_userManagerIsNull_shouldDoNothing() {
+ whenever(optionalUserManager.orElse(null)).thenReturn(null)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_intentResolverReturnsNull_shouldDoNothing() {
+ whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_flagDisabled_shouldDoNothing() {
+ createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_userIsLocked_shouldDoNothing() {
+ whenever(userManager.isUserUnlocked).thenReturn(false)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
new file mode 100644
index 0000000..f344c8d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.notetask
+
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.floating.FloatingTasks
+import java.util.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskController].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskInitializerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskInitializerTest : SysuiTestCase() {
+
+ @Mock lateinit var commandQueue: CommandQueue
+ @Mock lateinit var floatingTasks: FloatingTasks
+ @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(optionalFloatingTasks.isPresent).thenReturn(true)
+ whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
+ }
+
+ private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
+ return NoteTaskInitializer(
+ optionalFloatingTasks = optionalFloatingTasks,
+ lazyNoteTaskController = mock(),
+ commandQueue = commandQueue,
+ isEnabled = isEnabled,
+ )
+ }
+
+ @Test
+ fun initialize_shouldAddCallbacks() {
+ createNoteTaskInitializer().initialize()
+
+ verify(commandQueue).addCallback(any())
+ }
+
+ @Test
+ fun initialize_flagDisabled_shouldDoNothing() {
+ createNoteTaskInitializer(isEnabled = false).initialize()
+
+ verify(commandQueue, never()).addCallback(any())
+ }
+
+ @Test
+ fun initialize_floatingTasksNotPresent_shouldDoNothing() {
+ whenever(optionalFloatingTasks.isPresent).thenReturn(false)
+
+ createNoteTaskInitializer().initialize()
+
+ verify(commandQueue, never()).addCallback(any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
new file mode 100644
index 0000000..dd2cc2f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskIntentResolver].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskIntentResolverTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskIntentResolverTest : SysuiTestCase() {
+
+ @Mock lateinit var packageManager: PackageManager
+
+ private lateinit var resolver: NoteTaskIntentResolver
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ resolver = NoteTaskIntentResolver(packageManager)
+ }
+
+ private fun createResolveInfo(
+ packageName: String = "PackageName",
+ activityInfo: ActivityInfo? = null,
+ ): ResolveInfo {
+ return ResolveInfo().apply {
+ serviceInfo =
+ ServiceInfo().apply {
+ applicationInfo = ApplicationInfo().apply { this.packageName = packageName }
+ }
+ this.activityInfo = activityInfo
+ }
+ }
+
+ private fun createActivityInfo(
+ name: String? = "ActivityName",
+ exported: Boolean = true,
+ enabled: Boolean = true,
+ showWhenLocked: Boolean = true,
+ turnScreenOn: Boolean = true,
+ ): ActivityInfo {
+ return ActivityInfo().apply {
+ this.name = name
+ this.exported = exported
+ this.enabled = enabled
+ if (showWhenLocked) {
+ flags = flags or ActivityInfo.FLAG_SHOW_WHEN_LOCKED
+ }
+ if (turnScreenOn) {
+ flags = flags or ActivityInfo.FLAG_TURN_SCREEN_ON
+ }
+ }
+ }
+
+ private fun givenQueryIntentActivities(block: () -> List<ResolveInfo>) {
+ whenever(packageManager.queryIntentActivities(any(), any<ResolveInfoFlags>()))
+ .thenReturn(block())
+ }
+
+ private fun givenResolveActivity(block: () -> ResolveInfo?) {
+ whenever(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenReturn(block())
+ }
+
+ @Test
+ fun resolveIntent_shouldReturnNotesIntent() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo()) }
+
+ val actual = resolver.resolveIntent()
+
+ val expected =
+ Intent(NOTES_ACTION)
+ .setComponent(ComponentName("PackageName", "ActivityName"))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ // Compares the string representation of both intents, as they are different instances.
+ assertThat(actual.toString()).isEqualTo(expected.toString())
+ }
+
+ @Test
+ fun resolveIntent_activityInfoEnabledIsFalse_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity {
+ createResolveInfo(activityInfo = createActivityInfo(enabled = false))
+ }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoExportedIsFalse_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity {
+ createResolveInfo(activityInfo = createActivityInfo(exported = false))
+ }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoShowWhenLockedIsFalse_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity {
+ createResolveInfo(activityInfo = createActivityInfo(showWhenLocked = false))
+ }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoTurnScreenOnIsFalse_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity {
+ createResolveInfo(activityInfo = createActivityInfo(turnScreenOn = false))
+ }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoNameIsBlank_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = "")) }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoNameIsNull_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = null)) }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoIsNull_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { createResolveInfo(activityInfo = null) }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_resolveActivityIsNull_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { null }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_packageNameIsBlank_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo(packageName = "")) }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityNotFoundForAction_shouldReturnNull() {
+ givenQueryIntentActivities { emptyList() }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index bc27bbc..3131f60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.source.UserRecord
import org.junit.Assert.assertEquals
@@ -41,20 +42,27 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class UserDetailViewAdapterTest : SysuiTestCase() {
- @Mock private lateinit var mUserSwitcherController: UserSwitcherController
- @Mock private lateinit var mParent: ViewGroup
- @Mock private lateinit var mUserDetailItemView: UserDetailItemView
- @Mock private lateinit var mOtherView: View
- @Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView
- @Mock private lateinit var mLayoutInflater: LayoutInflater
+ @Mock
+ private lateinit var mUserSwitcherController: UserSwitcherController
+ @Mock
+ private lateinit var mParent: ViewGroup
+ @Mock
+ private lateinit var mUserDetailItemView: UserDetailItemView
+ @Mock
+ private lateinit var mOtherView: View
+ @Mock
+ private lateinit var mInflatedUserDetailItemView: UserDetailItemView
+ @Mock
+ private lateinit var mLayoutInflater: LayoutInflater
private var falsingManagerFake: FalsingManagerFake = FalsingManagerFake()
private lateinit var adapter: UserDetailView.Adapter
private lateinit var uiEventLogger: UiEventLoggerFake
@@ -67,10 +75,12 @@
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater)
`when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
- .thenReturn(mInflatedUserDetailItemView)
+ .thenReturn(mInflatedUserDetailItemView)
`when`(mParent.context).thenReturn(mContext)
- adapter = UserDetailView.Adapter(mContext, mUserSwitcherController, uiEventLogger,
- falsingManagerFake)
+ adapter = UserDetailView.Adapter(
+ mContext, mUserSwitcherController, uiEventLogger,
+ falsingManagerFake
+ )
mPicture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
}
@@ -145,6 +155,15 @@
assertNull(adapter.users.find { it.isManageUsers })
}
+ @Test
+ fun clickDismissDialog() {
+ val shower: UserSwitchDialogController.DialogShower =
+ mock(UserSwitchDialogController.DialogShower::class.java)
+ adapter.injectDialogShower(shower)
+ adapter.onUserListItemClicked(createUserRecord(current = true, guest = false), shower)
+ verify(shower).dismiss()
+ }
+
private fun createUserRecord(current: Boolean, guest: Boolean) =
UserRecord(
UserInfo(0 /* id */, "name", 0 /* flags */),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index d703705..2ef7312 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -5,6 +5,7 @@
import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT;
import static android.telephony.SignalStrength.SIGNAL_STRENGTH_POOR;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT;
import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_VERTICAL_WEIGHT;
import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX;
@@ -13,6 +14,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -38,6 +40,7 @@
import android.os.Handler;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
@@ -57,6 +60,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.UnreleasedFlag;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.connectivity.AccessPointController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -70,12 +75,15 @@
import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.ArrayList;
import java.util.List;
@@ -86,6 +94,9 @@
public class InternetDialogControllerTest extends SysuiTestCase {
private static final int SUB_ID = 1;
+ private static final int SUB_ID2 = 2;
+
+ private MockitoSession mStaticMockSession;
//SystemUIToast
private static final int GRAVITY_FLAGS = Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL;
@@ -158,6 +169,8 @@
private WifiStateWorker mWifiStateWorker;
@Mock
private SignalStrength mSignalStrength;
+ @Mock
+ private FeatureFlags mFlags;
private TestableResources mTestableResources;
private InternetDialogController mInternetDialogController;
@@ -167,6 +180,10 @@
@Before
public void setUp() {
+ mStaticMockSession = mockitoSession()
+ .mockStatic(SubscriptionManager.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
MockitoAnnotations.initMocks(this);
mTestableResources = mContext.getOrCreateTestableResources();
doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
@@ -183,6 +200,7 @@
mAccessPoints.add(mWifiEntry1);
when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+ when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(SUB_ID);
when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
.thenReturn(mSystemUIToast);
when(mSystemUIToast.getView()).thenReturn(mToastView);
@@ -196,7 +214,7 @@
mConnectivityManager, mHandler, mExecutor, mBroadcastDispatcher,
mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker,
- mLocationController, mDialogLaunchAnimator, mWifiStateWorker);
+ mLocationController, mDialogLaunchAnimator, mWifiStateWorker, mFlags);
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mInternetDialogController.mOnSubscriptionsChangedListener);
mInternetDialogController.onStart(mInternetDialogCallback, true);
@@ -205,6 +223,11 @@
mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
}
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
@Test
public void connectCarrierNetwork_mergedCarrierEntryCanConnect_connectAndCreateSysUiToast() {
when(mTelephonyManager.isDataEnabled()).thenReturn(true);
@@ -387,15 +410,45 @@
@Test
public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ InternetDialogController spyController = spy(mInternetDialogController);
fakeAirplaneModeEnabled(false);
when(mWifiStateWorker.isWifiEnabled()).thenReturn(true);
- mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+ spyController.onAccessPointsChanged(null /* accessPoints */);
+
+ doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
+ doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
+ doReturn(mServiceState).when(mTelephonyManager).getServiceState();
+ doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState();
+
+ assertFalse(TextUtils.equals(spyController.getSubtitleText(false),
+ getResourcesString("all_network_unavailable")));
+
+ doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .when(spyController).getActiveAutoSwitchNonDdsSubId();
+ spyController.onAccessPointsChanged(null /* accessPoints */);
+ assertTrue(TextUtils.equals(spyController.getSubtitleText(false),
+ getResourcesString("all_network_unavailable")));
+ }
+
+ @Test
+ public void getSubtitleText_withNoService_returnNoNetworksAvailable_flagOff() {
+ InternetDialogController spyController = spy(mInternetDialogController);
+ fakeAirplaneModeEnabled(false);
+ when(mWifiStateWorker.isWifiEnabled()).thenReturn(true);
+ spyController.onAccessPointsChanged(null /* accessPoints */);
doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
doReturn(mServiceState).when(mTelephonyManager).getServiceState();
doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState();
- assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
+ assertTrue(TextUtils.equals(spyController.getSubtitleText(false),
+ getResourcesString("all_network_unavailable")));
+
+ doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .when(spyController).getActiveAutoSwitchNonDdsSubId();
+ spyController.onAccessPointsChanged(null /* accessPoints */);
+ assertTrue(TextUtils.equals(spyController.getSubtitleText(false),
getResourcesString("all_network_unavailable")));
}
@@ -713,6 +766,108 @@
}
@Test
+ public void getSignalStrengthIcon_differentSubId() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ InternetDialogController spyController = spy(mInternetDialogController);
+ Drawable icons = spyController.getSignalStrengthIcon(SUB_ID, mContext, 1, 1, 0, false);
+ Drawable icons2 = spyController.getSignalStrengthIcon(SUB_ID2, mContext, 1, 1, 0, false);
+
+ assertThat(icons).isNotEqualTo(icons2);
+ }
+
+ @Test
+ public void getActiveAutoSwitchNonDdsSubId() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ // active on non-DDS
+ SubscriptionInfo info = mock(SubscriptionInfo.class);
+ doReturn(SUB_ID2).when(info).getSubscriptionId();
+ when(mSubscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(info);
+
+ int subId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+ assertThat(subId).isEqualTo(SUB_ID2);
+
+ // active on CBRS
+ doReturn(true).when(info).isOpportunistic();
+ subId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+ assertThat(subId).isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+ // active on DDS
+ doReturn(false).when(info).isOpportunistic();
+ doReturn(SUB_ID).when(info).getSubscriptionId();
+ when(mSubscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(info);
+
+ subId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+ assertThat(subId).isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ @Test
+ public void getActiveAutoSwitchNonDdsSubId_flagOff() {
+ // active on non-DDS
+ SubscriptionInfo info = mock(SubscriptionInfo.class);
+ doReturn(SUB_ID2).when(info).getSubscriptionId();
+ when(mSubscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(info);
+
+ int subId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+ assertThat(subId).isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ @Test
+ public void getMobileNetworkSummary() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ InternetDialogController spyController = spy(mInternetDialogController);
+ doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
+ doReturn(true).when(spyController).isMobileDataEnabled();
+ doReturn(true).when(spyController).activeNetworkIsCellular();
+ String dds = spyController.getMobileNetworkSummary(SUB_ID);
+ String nonDds = spyController.getMobileNetworkSummary(SUB_ID2);
+
+ assertThat(dds).contains(mContext.getString(R.string.mobile_data_poor_connection));
+ assertThat(dds).isNotEqualTo(nonDds);
+ }
+
+ @Test
+ public void getMobileNetworkSummary_flagOff() {
+ InternetDialogController spyController = spy(mInternetDialogController);
+ doReturn(true).when(spyController).isMobileDataEnabled();
+ doReturn(true).when(spyController).activeNetworkIsCellular();
+ String dds = spyController.getMobileNetworkSummary(SUB_ID);
+
+ assertThat(dds).contains(mContext.getString(R.string.mobile_data_connection_active));
+ }
+
+ @Test
+ public void launchMobileNetworkSettings_validSubId() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ InternetDialogController spyController = spy(mInternetDialogController);
+ doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
+ spyController.launchMobileNetworkSettings(mDialogLaunchView);
+
+ verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt(),
+ any());
+ }
+
+ @Test
+ public void launchMobileNetworkSettings_invalidSubId() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ InternetDialogController spyController = spy(mInternetDialogController);
+ doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .when(spyController).getActiveAutoSwitchNonDdsSubId();
+ spyController.launchMobileNetworkSettings(mDialogLaunchView);
+
+ verify(mActivityStarter, never())
+ .postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
+ }
+
+ @Test
+ public void setAutoDataSwitchMobileDataPolicy() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mInternetDialogController.setAutoDataSwitchMobileDataPolicy(SUB_ID, true);
+
+ verify(mTelephonyManager).setMobileDataPolicyEnabled(eq(
+ TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH), eq(true));
+ }
+
+ @Test
public void getSignalStrengthDrawableWithLevel_carrierNetworkIsNotActive_useMobileDataLevel() {
// Fake mobile data level as SIGNAL_STRENGTH_POOR(1)
when(mSignalStrength.getLevel()).thenReturn(SIGNAL_STRENGTH_POOR);
@@ -720,9 +875,9 @@
when(mInternetDialogController.getCarrierNetworkLevel()).thenReturn(WIFI_LEVEL_MAX);
InternetDialogController spyController = spy(mInternetDialogController);
- spyController.getSignalStrengthDrawableWithLevel(false /* isCarrierNetworkActive */);
+ spyController.getSignalStrengthDrawableWithLevel(false /* isCarrierNetworkActive */, 0);
- verify(spyController).getSignalStrengthIcon(any(), eq(SIGNAL_STRENGTH_POOR),
+ verify(spyController).getSignalStrengthIcon(eq(0), any(), eq(SIGNAL_STRENGTH_POOR),
eq(NUM_SIGNAL_STRENGTH_BINS), anyInt(), anyBoolean());
}
@@ -734,9 +889,9 @@
when(mInternetDialogController.getCarrierNetworkLevel()).thenReturn(WIFI_LEVEL_MAX);
InternetDialogController spyController = spy(mInternetDialogController);
- spyController.getSignalStrengthDrawableWithLevel(true /* isCarrierNetworkActive */);
+ spyController.getSignalStrengthDrawableWithLevel(true /* isCarrierNetworkActive */, 0);
- verify(spyController).getSignalStrengthIcon(any(), eq(WIFI_LEVEL_MAX),
+ verify(spyController).getSignalStrengthIcon(eq(0), any(), eq(WIFI_LEVEL_MAX),
eq(WIFI_LEVEL_MAX + 1), anyInt(), anyBoolean());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index f922475..4084cf4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -8,12 +8,15 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
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.AlertDialog;
+import android.content.DialogInterface;
import android.os.Handler;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
@@ -31,6 +34,7 @@
import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -72,6 +76,8 @@
private InternetDialogController mInternetDialogController;
@Mock
private KeyguardStateController mKeyguard;
+ @Mock
+ private DialogLaunchAnimator mDialogLaunchAnimator;
private FakeExecutor mBgExecutor = new FakeExecutor(new FakeSystemClock());
private InternetDialog mInternetDialog;
@@ -100,8 +106,9 @@
when(mInternetWifiEntry.hasInternetAccess()).thenReturn(true);
when(mWifiEntries.size()).thenReturn(1);
- when(mInternetDialogController.getMobileNetworkTitle()).thenReturn(MOBILE_NETWORK_TITLE);
- when(mInternetDialogController.getMobileNetworkSummary())
+ when(mInternetDialogController.getMobileNetworkTitle(anyInt()))
+ .thenReturn(MOBILE_NETWORK_TITLE);
+ when(mInternetDialogController.getMobileNetworkSummary(anyInt()))
.thenReturn(MOBILE_NETWORK_SUMMARY);
when(mInternetDialogController.isWifiEnabled()).thenReturn(true);
@@ -115,7 +122,8 @@
private void createInternetDialog() {
mInternetDialog = new InternetDialog(mContext, mock(InternetDialogFactory.class),
- mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler,
+ mInternetDialogController, true, true, true, mock(UiEventLogger.class),
+ mDialogLaunchAnimator, mHandler,
mBgExecutor, mKeyguard);
mInternetDialog.mAdapter = mInternetAdapter;
mInternetDialog.mConnectedWifiEntry = mInternetWifiEntry;
@@ -307,12 +315,18 @@
@Test
public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
+ mInternetDialog.dismissDialog();
+ doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ createInternetDialog();
// The preconditions WiFi ON and Internet WiFi are already in setUp()
doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
- mInternetDialog.updateDialog(false);
+ mInternetDialog.updateDialog(true);
assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+ LinearLayout secondaryLayout = mDialogView.requireViewById(
+ R.id.secondary_mobile_network_layout);
+ assertThat(secondaryLayout.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -460,6 +474,44 @@
}
@Test
+ public void updateDialog_showSecondaryDataSub() {
+ mInternetDialog.dismissDialog();
+ doReturn(1).when(mInternetDialogController).getActiveAutoSwitchNonDdsSubId();
+ doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ doReturn(false).when(mInternetDialogController).isAirplaneModeEnabled();
+ createInternetDialog();
+
+ clearInvocations(mInternetDialogController);
+ mInternetDialog.updateDialog(true);
+
+ LinearLayout primaryLayout = mDialogView.requireViewById(
+ R.id.mobile_network_layout);
+ LinearLayout secondaryLayout = mDialogView.requireViewById(
+ R.id.secondary_mobile_network_layout);
+
+ verify(mInternetDialogController).getMobileNetworkSummary(1);
+ assertThat(primaryLayout.getBackground()).isNotEqualTo(secondaryLayout.getBackground());
+
+ // Tap the primary sub info
+ primaryLayout.performClick();
+ ArgumentCaptor<AlertDialog> dialogArgumentCaptor =
+ ArgumentCaptor.forClass(AlertDialog.class);
+ verify(mDialogLaunchAnimator).showFromDialog(dialogArgumentCaptor.capture(),
+ eq(mInternetDialog), eq(null), eq(false));
+ AlertDialog dialog = dialogArgumentCaptor.getValue();
+ dialog.show();
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+ TestableLooper.get(this).processAllMessages();
+ verify(mInternetDialogController).setAutoDataSwitchMobileDataPolicy(1, false);
+
+ // Tap the secondary sub info
+ secondaryLayout.performClick();
+ verify(mInternetDialogController).launchMobileNetworkSettings(any(View.class));
+
+ dialog.dismiss();
+ }
+
+ @Test
public void updateDialog_wifiOn_hideWifiScanNotify() {
// The preconditions WiFi ON and WiFi entries are already in setUp()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index ffb41e5..70cbc64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Handler
+import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -104,13 +105,14 @@
mockContext,
mockPluginManager,
mockHandler,
- fakeDefaultProvider
+ isEnabled = true,
+ userHandle = UserHandle.USER_ALL,
+ defaultClockProvider = fakeDefaultProvider
) {
override var currentClockId: ClockId
get() = settingValue
set(value) { settingValue = value }
}
- registry.isEnabled = true
verify(mockPluginManager)
.addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java), eq(true))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 64dc956..4478039 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -25,6 +25,7 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -69,10 +70,13 @@
TransitionInfo combined = new TransitionInfoBuilder(TRANSIT_CLOSE)
.addChange(TRANSIT_CHANGE, FLAG_SHOW_WALLPAPER,
createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_STANDARD))
+ // Embedded TaskFragment should be excluded when animated with Task.
+ .addChange(TRANSIT_CLOSE, FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, null /* taskInfo */)
.addChange(TRANSIT_CLOSE, 0 /* flags */,
createTaskInfo(2 /* taskId */, ACTIVITY_TYPE_STANDARD))
.addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER, null /* taskInfo */)
- .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */).build();
+ .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */)
+ .build();
// Check apps extraction
RemoteAnimationTarget[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined,
mock(SurfaceControl.Transaction.class), null /* leashes */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
deleted file mode 100644
index 81b8e98..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification.row;
-
-import static org.mockito.Mockito.doReturn;
-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.when;
-
-import android.view.NotificationHeaderView;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.internal.widget.NotificationActionListLayout;
-import com.android.internal.widget.NotificationExpandButton;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.statusbar.notification.FeedbackIcon;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class NotificationContentViewTest extends SysuiTestCase {
-
- NotificationContentView mView;
-
- @Before
- @UiThreadTest
- public void setup() {
- mDependency.injectMockDependency(MediaOutputDialogFactory.class);
-
- mView = new NotificationContentView(mContext, null);
- ExpandableNotificationRow row = new ExpandableNotificationRow(mContext, null);
- ExpandableNotificationRow mockRow = spy(row);
- doReturn(10).when(mockRow).getIntrinsicHeight();
-
- mView.setContainingNotification(mockRow);
- mView.setHeights(10, 20, 30);
-
- mView.setContractedChild(createViewWithHeight(10));
- mView.setExpandedChild(createViewWithHeight(20));
- mView.setHeadsUpChild(createViewWithHeight(30));
-
- mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
- mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- }
-
- private View createViewWithHeight(int height) {
- View view = new View(mContext, null);
- view.setMinimumHeight(height);
- return view;
- }
-
- @Test
- @UiThreadTest
- public void testSetFeedbackIcon() {
- View mockContracted = mock(NotificationHeaderView.class);
- when(mockContracted.findViewById(com.android.internal.R.id.feedback))
- .thenReturn(mockContracted);
- when(mockContracted.getContext()).thenReturn(mContext);
- View mockExpanded = mock(NotificationHeaderView.class);
- when(mockExpanded.findViewById(com.android.internal.R.id.feedback))
- .thenReturn(mockExpanded);
- when(mockExpanded.getContext()).thenReturn(mContext);
- View mockHeadsUp = mock(NotificationHeaderView.class);
- when(mockHeadsUp.findViewById(com.android.internal.R.id.feedback))
- .thenReturn(mockHeadsUp);
- when(mockHeadsUp.getContext()).thenReturn(mContext);
-
- mView.setContractedChild(mockContracted);
- mView.setExpandedChild(mockExpanded);
- mView.setHeadsUpChild(mockHeadsUp);
-
- mView.setFeedbackIcon(new FeedbackIcon(R.drawable.ic_feedback_alerted,
- R.string.notification_feedback_indicator_alerted));
-
- verify(mockContracted, times(1)).setVisibility(View.VISIBLE);
- verify(mockExpanded, times(1)).setVisibility(View.VISIBLE);
- verify(mockHeadsUp, times(1)).setVisibility(View.VISIBLE);
- }
-
- @Test
- @UiThreadTest
- public void testExpandButtonFocusIsCalled() {
- View mockContractedEB = mock(NotificationExpandButton.class);
- View mockContracted = mock(NotificationHeaderView.class);
- when(mockContracted.animate()).thenReturn(mock(ViewPropertyAnimator.class));
- when(mockContracted.findViewById(com.android.internal.R.id.expand_button)).thenReturn(
- mockContractedEB);
- when(mockContracted.getContext()).thenReturn(mContext);
-
- View mockExpandedEB = mock(NotificationExpandButton.class);
- View mockExpanded = mock(NotificationHeaderView.class);
- when(mockExpanded.animate()).thenReturn(mock(ViewPropertyAnimator.class));
- when(mockExpanded.findViewById(com.android.internal.R.id.expand_button)).thenReturn(
- mockExpandedEB);
- when(mockExpanded.getContext()).thenReturn(mContext);
-
- View mockHeadsUpEB = mock(NotificationExpandButton.class);
- View mockHeadsUp = mock(NotificationHeaderView.class);
- when(mockHeadsUp.animate()).thenReturn(mock(ViewPropertyAnimator.class));
- when(mockHeadsUp.findViewById(com.android.internal.R.id.expand_button)).thenReturn(
- mockHeadsUpEB);
- when(mockHeadsUp.getContext()).thenReturn(mContext);
-
- // Set up all 3 child forms
- mView.setContractedChild(mockContracted);
- mView.setExpandedChild(mockExpanded);
- mView.setHeadsUpChild(mockHeadsUp);
-
- // This is required to call requestAccessibilityFocus()
- mView.setFocusOnVisibilityChange();
-
- // The following will initialize the view and switch from not visible to expanded.
- // (heads-up is actually an alternate form of contracted, hence this enters expanded state)
- mView.setHeadsUp(true);
-
- verify(mockContractedEB, times(0)).requestAccessibilityFocus();
- verify(mockExpandedEB, times(1)).requestAccessibilityFocus();
- verify(mockHeadsUpEB, times(0)).requestAccessibilityFocus();
- }
-
- @Test
- @UiThreadTest
- public void testRemoteInputVisibleSetsActionsUnimportantHideDescendantsForAccessibility() {
- View mockContracted = mock(NotificationHeaderView.class);
-
- View mockExpandedActions = mock(NotificationActionListLayout.class);
- View mockExpanded = mock(NotificationHeaderView.class);
- when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn(
- mockExpandedActions);
-
- View mockHeadsUpActions = mock(NotificationActionListLayout.class);
- View mockHeadsUp = mock(NotificationHeaderView.class);
- when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn(
- mockHeadsUpActions);
-
- mView.setContractedChild(mockContracted);
- mView.setExpandedChild(mockExpanded);
- mView.setHeadsUpChild(mockHeadsUp);
-
- mView.setRemoteInputVisible(true);
-
- verify(mockContracted, times(0)).findViewById(0);
- verify(mockExpandedActions, times(1)).setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
- verify(mockHeadsUpActions, times(1)).setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
- }
-
- @Test
- @UiThreadTest
- public void testRemoteInputInvisibleSetsActionsAutoImportantForAccessibility() {
- View mockContracted = mock(NotificationHeaderView.class);
-
- View mockExpandedActions = mock(NotificationActionListLayout.class);
- View mockExpanded = mock(NotificationHeaderView.class);
- when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn(
- mockExpandedActions);
-
- View mockHeadsUpActions = mock(NotificationActionListLayout.class);
- View mockHeadsUp = mock(NotificationHeaderView.class);
- when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn(
- mockHeadsUpActions);
-
- mView.setContractedChild(mockContracted);
- mView.setExpandedChild(mockExpanded);
- mView.setHeadsUpChild(mockHeadsUp);
-
- mView.setRemoteInputVisible(false);
-
- verify(mockContracted, times(0)).findViewById(0);
- verify(mockExpandedActions, times(1)).setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
- verify(mockHeadsUpActions, times(1)).setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
new file mode 100644
index 0000000..562b4df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.res.Resources
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import android.view.NotificationHeaderView
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.widget.NotificationActionListLayout
+import com.android.internal.widget.NotificationExpandButton
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.statusbar.notification.FeedbackIcon
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationContentViewTest : SysuiTestCase() {
+ private lateinit var view: NotificationContentView
+
+ @Mock private lateinit var mPeopleNotificationIdentifier: PeopleNotificationIdentifier
+
+ private val notificationContentMargin =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_content_margin)
+
+ @Before
+ fun setup() {
+ initMocks(this)
+
+ mDependency.injectMockDependency(MediaOutputDialogFactory::class.java)
+
+ view = spy(NotificationContentView(mContext, /* attrs= */ null))
+ val row = ExpandableNotificationRow(mContext, /* attrs= */ null)
+ row.entry = createMockNotificationEntry(false)
+ val spyRow = spy(row)
+ doReturn(10).whenever(spyRow).intrinsicHeight
+
+ with(view) {
+ initialize(mPeopleNotificationIdentifier, mock(), mock(), mock())
+ setContainingNotification(spyRow)
+ setHeights(/* smallHeight= */ 10, /* headsUpMaxHeight= */ 20, /* maxHeight= */ 30)
+ contractedChild = createViewWithHeight(10)
+ expandedChild = createViewWithHeight(20)
+ headsUpChild = createViewWithHeight(30)
+ measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+ layout(0, 0, view.measuredWidth, view.measuredHeight)
+ }
+ }
+
+ private fun createViewWithHeight(height: Int) =
+ View(mContext, /* attrs= */ null).apply { minimumHeight = height }
+
+ @Test
+ fun testSetFeedbackIcon() {
+ // Given: contractedChild, enpandedChild, and headsUpChild being set
+ val mockContracted = createMockNotificationHeaderView()
+ val mockExpanded = createMockNotificationHeaderView()
+ val mockHeadsUp = createMockNotificationHeaderView()
+
+ with(view) {
+ contractedChild = mockContracted
+ expandedChild = mockExpanded
+ headsUpChild = mockHeadsUp
+ }
+
+ // When: FeedBackIcon is set
+ view.setFeedbackIcon(
+ FeedbackIcon(
+ R.drawable.ic_feedback_alerted,
+ R.string.notification_feedback_indicator_alerted
+ )
+ )
+
+ // Then: contractedChild, enpandedChild, and headsUpChild should be set to be visible
+ verify(mockContracted).visibility = View.VISIBLE
+ verify(mockExpanded).visibility = View.VISIBLE
+ verify(mockHeadsUp).visibility = View.VISIBLE
+ }
+
+ private fun createMockNotificationHeaderView() =
+ mock<NotificationHeaderView>().apply {
+ whenever(this.findViewById<View>(R.id.feedback)).thenReturn(this)
+ whenever(this.context).thenReturn(mContext)
+ }
+
+ @Test
+ fun testExpandButtonFocusIsCalled() {
+ val mockContractedEB = mock<NotificationExpandButton>()
+ val mockContracted = createMockNotificationHeaderView(mockContractedEB)
+
+ val mockExpandedEB = mock<NotificationExpandButton>()
+ val mockExpanded = createMockNotificationHeaderView(mockExpandedEB)
+
+ val mockHeadsUpEB = mock<NotificationExpandButton>()
+ val mockHeadsUp = createMockNotificationHeaderView(mockHeadsUpEB)
+
+ // Set up all 3 child forms
+ view.contractedChild = mockContracted
+ view.expandedChild = mockExpanded
+ view.headsUpChild = mockHeadsUp
+
+ // This is required to call requestAccessibilityFocus()
+ view.setFocusOnVisibilityChange()
+
+ // The following will initialize the view and switch from not visible to expanded.
+ // (heads-up is actually an alternate form of contracted, hence this enters expanded state)
+ view.setHeadsUp(true)
+ verify(mockContractedEB, never()).requestAccessibilityFocus()
+ verify(mockExpandedEB).requestAccessibilityFocus()
+ verify(mockHeadsUpEB, never()).requestAccessibilityFocus()
+ }
+
+ private fun createMockNotificationHeaderView(mockExpandedEB: NotificationExpandButton) =
+ mock<NotificationHeaderView>().apply {
+ whenever(this.animate()).thenReturn(mock())
+ whenever(this.findViewById<View>(R.id.expand_button)).thenReturn(mockExpandedEB)
+ whenever(this.context).thenReturn(mContext)
+ }
+
+ @Test
+ fun testRemoteInputVisibleSetsActionsUnimportantHideDescendantsForAccessibility() {
+ val mockContracted = mock<NotificationHeaderView>()
+
+ val mockExpandedActions = mock<NotificationActionListLayout>()
+ val mockExpanded = mock<NotificationHeaderView>()
+ whenever(mockExpanded.findViewById<View>(R.id.actions)).thenReturn(mockExpandedActions)
+
+ val mockHeadsUpActions = mock<NotificationActionListLayout>()
+ val mockHeadsUp = mock<NotificationHeaderView>()
+ whenever(mockHeadsUp.findViewById<View>(R.id.actions)).thenReturn(mockHeadsUpActions)
+
+ with(view) {
+ contractedChild = mockContracted
+ expandedChild = mockExpanded
+ headsUpChild = mockHeadsUp
+ }
+
+ view.setRemoteInputVisible(true)
+
+ verify(mockContracted, never()).findViewById<View>(0)
+ verify(mockExpandedActions).importantForAccessibility =
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ verify(mockHeadsUpActions).importantForAccessibility =
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ }
+
+ @Test
+ fun testRemoteInputInvisibleSetsActionsAutoImportantForAccessibility() {
+ val mockContracted = mock<NotificationHeaderView>()
+
+ val mockExpandedActions = mock<NotificationActionListLayout>()
+ val mockExpanded = mock<NotificationHeaderView>()
+ whenever(mockExpanded.findViewById<View>(R.id.actions)).thenReturn(mockExpandedActions)
+
+ val mockHeadsUpActions = mock<NotificationActionListLayout>()
+ val mockHeadsUp = mock<NotificationHeaderView>()
+ whenever(mockHeadsUp.findViewById<View>(R.id.actions)).thenReturn(mockHeadsUpActions)
+
+ with(view) {
+ contractedChild = mockContracted
+ expandedChild = mockExpanded
+ headsUpChild = mockHeadsUp
+ }
+
+ view.setRemoteInputVisible(false)
+
+ verify(mockContracted, never()).findViewById<View>(0)
+ verify(mockExpandedActions).importantForAccessibility =
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ verify(mockHeadsUpActions).importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+
+ @Test
+ fun setExpandedChild_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
+ // Given: bottom margin of actionListMarginTarget is notificationContentMargin
+ // Bubble button should not be shown for the given NotificationEntry
+ val mockNotificationEntry = createMockNotificationEntry(/* showButton= */ false)
+ val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
+ val actionListMarginTarget =
+ spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
+ val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+ whenever(
+ mockExpandedChild.findViewById<LinearLayout>(
+ R.id.notification_action_list_margin_target
+ )
+ )
+ .thenReturn(actionListMarginTarget)
+ view.setContainingNotification(mockContainingNotification)
+
+ // When: call NotificationContentView.setExpandedChild() to set the expandedChild
+ view.expandedChild = mockExpandedChild
+
+ // Then: bottom margin of actionListMarginTarget should not change,
+ // still be notificationContentMargin
+ assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
+ }
+
+ @Test
+ fun setExpandedChild_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
+ // Given: bottom margin of actionListMarginTarget is notificationContentMargin
+ // Bubble button should be shown for the given NotificationEntry
+ val mockNotificationEntry = createMockNotificationEntry(/* showButton= */ true)
+ val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
+ val actionListMarginTarget =
+ spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
+ val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+ whenever(
+ mockExpandedChild.findViewById<LinearLayout>(
+ R.id.notification_action_list_margin_target
+ )
+ )
+ .thenReturn(actionListMarginTarget)
+ view.setContainingNotification(mockContainingNotification)
+
+ // When: call NotificationContentView.setExpandedChild() to set the expandedChild
+ view.expandedChild = mockExpandedChild
+
+ // Then: bottom margin of actionListMarginTarget should be set to 0
+ assertEquals(0, getMarginBottom(actionListMarginTarget))
+ }
+
+ @Test
+ fun onNotificationUpdated_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
+ // Given: bottom margin of actionListMarginTarget is notificationContentMargin
+ val mockNotificationEntry = createMockNotificationEntry(/* showButton= */ false)
+ val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
+ val actionListMarginTarget =
+ spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
+ val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+ whenever(
+ mockExpandedChild.findViewById<LinearLayout>(
+ R.id.notification_action_list_margin_target
+ )
+ )
+ .thenReturn(actionListMarginTarget)
+ view.setContainingNotification(mockContainingNotification)
+ view.expandedChild = mockExpandedChild
+ assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
+
+ // When: call NotificationContentView.onNotificationUpdated() to update the
+ // NotificationEntry, which should not show bubble button
+ view.onNotificationUpdated(createMockNotificationEntry(/* showButton= */ false))
+
+ // Then: bottom margin of actionListMarginTarget should not change, still be 20
+ assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
+ }
+
+ @Test
+ fun onNotificationUpdated_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
+ // Given: bottom margin of actionListMarginTarget is notificationContentMargin
+ val mockNotificationEntry = createMockNotificationEntry(/* showButton= */ false)
+ val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
+ val actionListMarginTarget =
+ spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
+ val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+ whenever(
+ mockExpandedChild.findViewById<LinearLayout>(
+ R.id.notification_action_list_margin_target
+ )
+ )
+ .thenReturn(actionListMarginTarget)
+ view.setContainingNotification(mockContainingNotification)
+ view.expandedChild = mockExpandedChild
+ assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
+
+ // When: call NotificationContentView.onNotificationUpdated() to update the
+ // NotificationEntry, which should show bubble button
+ view.onNotificationUpdated(createMockNotificationEntry(true))
+
+ // Then: bottom margin of actionListMarginTarget should not change, still be 20
+ assertEquals(0, getMarginBottom(actionListMarginTarget))
+ }
+
+ private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
+ mock<ExpandableNotificationRow>().apply {
+ whenever(this.entry).thenReturn(notificationEntry)
+ whenever(this.context).thenReturn(mContext)
+ whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {})
+ }
+
+ private fun createMockNotificationEntry(showButton: Boolean) =
+ mock<NotificationEntry>().apply {
+ whenever(mPeopleNotificationIdentifier.getPeopleNotificationType(this))
+ .thenReturn(PeopleNotificationIdentifier.TYPE_FULL_PERSON)
+ whenever(this.bubbleMetadata).thenReturn(mock())
+ val sbnMock: StatusBarNotification = mock()
+ val userMock: UserHandle = mock()
+ whenever(this.sbn).thenReturn(sbnMock)
+ whenever(sbnMock.user).thenReturn(userMock)
+ doReturn(showButton).whenever(view).shouldShowBubbleButton(this)
+ }
+
+ private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
+ val outerLayout = LinearLayout(mContext)
+ val innerLayout = LinearLayout(mContext)
+ outerLayout.addView(innerLayout)
+ val mlp = innerLayout.layoutParams as ViewGroup.MarginLayoutParams
+ mlp.setMargins(0, 0, 0, bottomMargin)
+ return innerLayout
+ }
+
+ private fun createMockExpandedChild(notificationEntry: NotificationEntry) =
+ mock<ExpandableNotificationRow>().apply {
+ whenever(this.findViewById<ImageView>(R.id.bubble_button)).thenReturn(mock())
+ whenever(this.findViewById<View>(R.id.actions_container)).thenReturn(mock())
+ whenever(this.entry).thenReturn(notificationEntry)
+ whenever(this.context).thenReturn(mContext)
+
+ val resourcesMock: Resources = mock()
+ whenever(resourcesMock.configuration).thenReturn(mock())
+ whenever(this.resources).thenReturn(resourcesMock)
+ }
+
+ private fun getMarginBottom(layout: LinearLayout): Int =
+ (layout.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 91aecd8..dceb4ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -78,6 +78,7 @@
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -89,6 +90,7 @@
/**
* Tests for {@link NotificationStackScrollLayout}.
*/
+@Ignore("b/255552856")
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 6de8bd5..5755782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -235,6 +235,7 @@
@Mock private NavigationBarController mNavigationBarController;
@Mock private AccessibilityFloatingMenuController mAccessibilityFloatingMenuController;
@Mock private SysuiColorExtractor mColorExtractor;
+ private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock private ColorExtractor.GradientColors mGradientColors;
@Mock private PulseExpansionHandler mPulseExpansionHandler;
@Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
@@ -366,10 +367,10 @@
return null;
}).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
- WakefulnessLifecycle wakefulnessLifecycle =
+ mWakefulnessLifecycle =
new WakefulnessLifecycle(mContext, mIWallpaperManager, mDumpManager);
- wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
- wakefulnessLifecycle.dispatchFinishedWakingUp();
+ mWakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
+ mWakefulnessLifecycle.dispatchFinishedWakingUp();
when(mGradientColors.supportsDarkText()).thenReturn(true);
when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
@@ -428,7 +429,7 @@
mBatteryController,
mColorExtractor,
new ScreenLifecycle(mDumpManager),
- wakefulnessLifecycle,
+ mWakefulnessLifecycle,
mStatusBarStateController,
Optional.of(mBubbles),
mDeviceProvisionedController,
@@ -507,6 +508,8 @@
mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController;
mCentralSurfaces.mBarService = mBarService;
mCentralSurfaces.mStackScroller = mStackScroller;
+ mCentralSurfaces.mGestureWakeLock = mPowerManager.newWakeLock(
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "sysui:GestureWakeLock");
mCentralSurfaces.startKeyguard();
mInitController.executePostInitTasks();
notificationLogger.setUpWithContainer(mNotificationListContainer);
@@ -1125,6 +1128,55 @@
assertThat(onDismissActionCaptor.getValue().onDismiss()).isFalse();
}
+ @Test
+ public void testKeyguardHideDelayedIfOcclusionAnimationRunning() {
+ // Show the keyguard and verify we've done so.
+ setKeyguardShowingAndOccluded(true /* showing */, false /* occluded */);
+ verify(mStatusBarStateController).setState(StatusBarState.KEYGUARD);
+
+ // Request to hide the keyguard, but while the occlude animation is playing. We should delay
+ // this hide call, since we're playing the occlude animation over the keyguard and thus want
+ // it to remain visible.
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
+ setKeyguardShowingAndOccluded(false /* showing */, true /* occluded */);
+ verify(mStatusBarStateController, never()).setState(StatusBarState.SHADE);
+
+ // Once the animation ends, verify that the keyguard is actually hidden.
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(false);
+ setKeyguardShowingAndOccluded(false /* showing */, true /* occluded */);
+ verify(mStatusBarStateController).setState(StatusBarState.SHADE);
+ }
+
+ @Test
+ public void testKeyguardHideNotDelayedIfOcclusionAnimationNotRunning() {
+ // Show the keyguard and verify we've done so.
+ setKeyguardShowingAndOccluded(true /* showing */, false /* occluded */);
+ verify(mStatusBarStateController).setState(StatusBarState.KEYGUARD);
+
+ // Hide the keyguard while the occlusion animation is not running. Verify that we
+ // immediately hide the keyguard.
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(false);
+ setKeyguardShowingAndOccluded(false /* showing */, true /* occluded */);
+ verify(mStatusBarStateController).setState(StatusBarState.SHADE);
+ }
+
+ /**
+ * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
+ * to reconfigure the keyguard to reflect the requested showing/occluded states.
+ */
+ private void setKeyguardShowingAndOccluded(boolean showing, boolean occluded) {
+ when(mStatusBarStateController.isKeyguardRequested()).thenReturn(showing);
+ when(mKeyguardStateController.isOccluded()).thenReturn(occluded);
+
+ // If we want to show the keyguard, make sure that we think we're awake and not unlocking.
+ if (showing) {
+ when(mBiometricUnlockController.isWakeAndUnlock()).thenReturn(false);
+ mWakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
+ }
+
+ mCentralSurfaces.updateIsKeyguard(false /* forceStateChange */);
+ }
+
private void setDeviceState(int state) {
ArgumentCaptor<DeviceStateManager.DeviceStateCallback> callbackCaptor =
ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 4d1a52c..a5deaa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.ScrimController.KEYGUARD_SCRIM_ALPHA;
import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
@@ -58,6 +59,7 @@
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -117,6 +119,7 @@
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private KeyguardViewMediator mKeyguardViewMediator;
private static class AnimatorListener implements Animator.AnimatorListener {
private int mNumStarts;
@@ -230,7 +233,8 @@
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager);
+ mStatusBarKeyguardViewManager,
+ mKeyguardViewMediator);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -239,6 +243,8 @@
mScrimController.setWallpaperSupportsAmbientMode(false);
mScrimController.transitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
+
+ mScrimController.setLaunchingAffordanceWithPreview(false);
}
@After
@@ -852,7 +858,8 @@
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager);
+ mStatusBarKeyguardViewManager,
+ mKeyguardViewMediator);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1592,6 +1599,30 @@
assertScrimAlpha(mScrimBehind, 0);
}
+ @Test
+ public void keyguardAlpha_whenUnlockedForOcclusion_ifPlayingOcclusionAnimation() {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
+
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ finishAnimationsImmediately();
+
+ assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
+ }
+
+ @Test
+ public void keyguardAlpha_whenUnlockedForLaunch_ifLaunchingAffordance() {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
+ mScrimController.setLaunchingAffordanceWithPreview(true);
+
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ finishAnimationsImmediately();
+
+ assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
+ }
+
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
index fa7b259..9957c2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
@@ -14,8 +14,6 @@
import com.android.internal.view.AppearanceRegion
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.SysuiStatusBarStateController
import org.junit.Before
import org.junit.Test
@@ -40,7 +38,6 @@
@Mock private lateinit var lightBarController: LightBarController
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var letterboxAppearanceCalculator: LetterboxAppearanceCalculator
- @Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var centralSurfaces: CentralSurfaces
private lateinit var sysBarAttrsListener: SystemBarAttributesListener
@@ -57,7 +54,6 @@
sysBarAttrsListener =
SystemBarAttributesListener(
centralSurfaces,
- featureFlags,
letterboxAppearanceCalculator,
statusBarStateController,
lightBarController,
@@ -74,18 +70,14 @@
}
@Test
- fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToCentralSurfaces() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
-
+ fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToCentralSurfaces() {
changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS)
verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance)
}
@Test
- fun onSysBarAttrsChanged_flagTrue_noLetterbox_forwardsOriginalAppearanceToCtrlSrfcs() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
-
+ fun onSysBarAttrsChanged_noLetterbox_forwardsOriginalAppearanceToCtrlSrfcs() {
changeSysBarAttrs(TEST_APPEARANCE, arrayOf<LetterboxDetails>())
verify(centralSurfaces).setAppearance(TEST_APPEARANCE)
@@ -100,9 +92,7 @@
}
@Test
- fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToStatusBarStateCtrl() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
-
+ fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToStatusBarStateCtrl() {
changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS)
verify(statusBarStateController)
@@ -120,9 +110,7 @@
}
@Test
- fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToLightBarController() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
-
+ fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToLightBarController() {
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
verify(lightBarController)
@@ -135,7 +123,6 @@
@Test
fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToStatusBarStateController() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
reset(centralSurfaces, lightBarController, statusBarStateController)
@@ -148,7 +135,6 @@
@Test
fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToLightBarController() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
reset(centralSurfaces, lightBarController, statusBarStateController)
@@ -164,7 +150,6 @@
@Test
fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToCentralSurfaces() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
reset(centralSurfaces, lightBarController, statusBarStateController)
@@ -175,7 +160,6 @@
@Test
fun onStatusBarBoundsChanged_previousCallEmptyLetterbox_doesNothing() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf())
reset(centralSurfaces, lightBarController, statusBarStateController)
@@ -184,17 +168,6 @@
verifyZeroInteractions(centralSurfaces, lightBarController, statusBarStateController)
}
- @Test
- fun onStatusBarBoundsChanged_flagFalse_doesNothing() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(false)
- changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
- reset(centralSurfaces, lightBarController, statusBarStateController)
-
- sysBarAttrsListener.onStatusBarBoundsChanged()
-
- verifyZeroInteractions(centralSurfaces, lightBarController, statusBarStateController)
- }
-
private fun changeSysBarAttrs(@Appearance appearance: Int) {
changeSysBarAttrs(appearance, arrayOf<LetterboxDetails>())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
new file mode 100644
index 0000000..6ff7b7c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileConnectionRepository : MobileConnectionRepository {
+ private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
+ override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
+
+ fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
+ _subscriptionsModelFlow.value = model
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
similarity index 66%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 0d15268..c88d468 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -18,11 +18,11 @@
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.settingslib.mobile.MobileMappings.Config
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
+class FakeMobileConnectionsRepository : MobileConnectionsRepository {
private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
@@ -30,22 +30,27 @@
MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
- private val subIdFlows = mutableMapOf<Int, MutableStateFlow<MobileSubscriptionModel>>()
- override fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> {
- return subIdFlows[subId]
- ?: MutableStateFlow(MobileSubscriptionModel()).also { subIdFlows[subId] = it }
+ private val _defaultDataSubRatConfig = MutableStateFlow(Config())
+ override val defaultDataSubRatConfig = _defaultDataSubRatConfig
+
+ private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
+ override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
+ return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
}
fun setSubscriptions(subs: List<SubscriptionInfo>) {
_subscriptionsFlow.value = subs
}
+ fun setDefaultDataSubRatConfig(config: Config) {
+ _defaultDataSubRatConfig.value = config
+ }
+
fun setActiveMobileDataSubscriptionId(subId: Int) {
_activeMobileDataSubscriptionId.value = subId
}
- fun setMobileSubscriptionModel(model: MobileSubscriptionModel, subId: Int) {
- val subscription = subIdFlows[subId] ?: throw Exception("no flow exists for this subId yet")
- subscription.value = model
+ fun setMobileConnectionRepositoryForId(subId: Int, repo: MobileConnectionRepository) {
+ subIdRepos[subId] = repo
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
new file mode 100644
index 0000000..775e6db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileConnectionRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: MobileConnectionRepositoryImpl
+
+ @Mock private lateinit var subscriptionManager: SubscriptionManager
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
+
+ underTest =
+ MobileConnectionRepositoryImpl(
+ SUB_1_ID,
+ telephonyManager,
+ IMMEDIATE,
+ logger,
+ scope,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_default() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(MobileSubscriptionModel())
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_emergencyOnly() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+ assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_emergencyOnly_toggles() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<ServiceStateListener>()
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+ callback.onServiceStateChanged(serviceState)
+ serviceState.isEmergencyOnly = false
+ callback.onServiceStateChanged(serviceState)
+
+ assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_signalStrengths_levelsUpdate() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest?.isGsm).isEqualTo(true)
+ assertThat(latest?.primaryLevel).isEqualTo(1)
+ assertThat(latest?.cdmaLevel).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataConnectionState() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(100, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(100)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataActivity() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>()
+ callback.onDataActivity(3)
+
+ assertThat(latest?.dataActivityDirection).isEqualTo(3)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_carrierNetworkChange() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
+ callback.onCarrierNetworkChange(true)
+
+ assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun subscriptionFlow_networkType_default() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val type = NETWORK_TYPE_UNKNOWN
+ val expected = DefaultNetworkType(type)
+
+ assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun subscriptionFlow_networkType_updatesUsingDefault() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val type = NETWORK_TYPE_LTE
+ val expected = DefaultNetworkType(type)
+ val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+ callback.onDisplayInfoChanged(ti)
+
+ assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun subscriptionFlow_networkType_updatesUsingOverride() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val type = OVERRIDE_NETWORK_TYPE_LTE_CA
+ val expected = OverrideNetworkType(type)
+ val ti =
+ mock<TelephonyDisplayInfo>().also {
+ whenever(it.overrideNetworkType).thenReturn(type)
+ }
+ callback.onDisplayInfoChanged(ti)
+
+ assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+ val callbackCaptor = argumentCaptor<TelephonyCallback>()
+ Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.allValues
+ }
+
+ private inline fun <reified T> getTelephonyCallbackForType(): T {
+ val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+ assertThat(cbs.size).isEqualTo(1)
+ return cbs[0]
+ }
+
+ /** Convenience constructor for SignalStrength */
+ private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
+ val signalStrength = mock<SignalStrength>()
+ whenever(signalStrength.isGsm).thenReturn(isGsm)
+ whenever(signalStrength.level).thenReturn(gsmLevel)
+ val cdmaStrength =
+ mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
+ whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
+ .thenReturn(listOf(cdmaStrength))
+
+ return signalStrength
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
new file mode 100644
index 0000000..326e0d281
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileConnectionsRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: MobileConnectionsRepositoryImpl
+
+ @Mock private lateinit var subscriptionManager: SubscriptionManager
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ ArgumentMatchers.anyInt(),
+ nullable(),
+ )
+ )
+ .thenReturn(flowOf(Unit))
+
+ underTest =
+ MobileConnectionsRepositoryImpl(
+ subscriptionManager,
+ telephonyManager,
+ logger,
+ broadcastDispatcher,
+ context,
+ IMMEDIATE,
+ scope,
+ mock(),
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_initiallyEmpty() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
+ }
+
+ @Test
+ fun testSubscriptions_listUpdates() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_removingSub_updatesList() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ // WHEN 2 networks show up
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // WHEN one network is removed
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // THEN the subscriptions list represents the newest change
+ assertThat(latest).isEqualTo(listOf(SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.activeMobileDataSubscriptionId.value)
+ .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_updates() =
+ runBlocking(IMMEDIATE) {
+ var active: Int? = null
+
+ val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(active).isEqualTo(SUB_2_ID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testConnectionRepository_validSubId_isCached() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.subscriptionsFlow.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+ val repo2 = underTest.getRepoForSubId(SUB_1_ID)
+
+ assertThat(repo1).isSameInstanceAs(repo2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testConnectionCache_clearsInvalidSubscriptions() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.subscriptionsFlow.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Get repos to trigger caching
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+ val repo2 = underTest.getRepoForSubId(SUB_2_ID)
+
+ assertThat(underTest.getSubIdRepoCache())
+ .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
+
+ // SUB_2 disappears
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testConnectionRepository_invalidSubId_throws() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.subscriptionsFlow.launchIn(this)
+
+ assertThrows(IllegalArgumentException::class.java) {
+ underTest.getRepoForSubId(SUB_1_ID)
+ }
+
+ job.cancel()
+ }
+
+ private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+ val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+ verify(subscriptionManager)
+ .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
+ private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+ val callbackCaptor = argumentCaptor<TelephonyCallback>()
+ verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.allValues
+ }
+
+ private inline fun <reified T> getTelephonyCallbackForType(): T {
+ val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+ assertThat(cbs.size).isEqualTo(1)
+ return cbs[0]
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
deleted file mode 100644
index 316b795..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
-
-import android.telephony.CellSignalStrengthCdma
-import android.telephony.ServiceState
-import android.telephony.SignalStrength
-import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
-import android.telephony.TelephonyCallback
-import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
-import android.telephony.TelephonyCallback.CarrierNetworkListener
-import android.telephony.TelephonyCallback.DataActivityListener
-import android.telephony.TelephonyCallback.DataConnectionStateListener
-import android.telephony.TelephonyCallback.DisplayInfoListener
-import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyCallback.SignalStrengthsListener
-import android.telephony.TelephonyDisplayInfo
-import android.telephony.TelephonyManager
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-class MobileSubscriptionRepositoryTest : SysuiTestCase() {
- private lateinit var underTest: MobileSubscriptionRepositoryImpl
-
- @Mock private lateinit var subscriptionManager: SubscriptionManager
- @Mock private lateinit var telephonyManager: TelephonyManager
- private val scope = CoroutineScope(IMMEDIATE)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- underTest =
- MobileSubscriptionRepositoryImpl(
- subscriptionManager,
- telephonyManager,
- IMMEDIATE,
- scope,
- )
- }
-
- @After
- fun tearDown() {
- scope.cancel()
- }
-
- @Test
- fun testSubscriptions_initiallyEmpty() =
- runBlocking(IMMEDIATE) {
- assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
- }
-
- @Test
- fun testSubscriptions_listUpdates() =
- runBlocking(IMMEDIATE) {
- var latest: List<SubscriptionInfo>? = null
-
- val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
-
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1, SUB_2))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
-
- job.cancel()
- }
-
- @Test
- fun testSubscriptions_removingSub_updatesList() =
- runBlocking(IMMEDIATE) {
- var latest: List<SubscriptionInfo>? = null
-
- val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
-
- // WHEN 2 networks show up
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1, SUB_2))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- // WHEN one network is removed
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_2))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- // THEN the subscriptions list represents the newest change
- assertThat(latest).isEqualTo(listOf(SUB_2))
-
- job.cancel()
- }
-
- @Test
- fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
- runBlocking(IMMEDIATE) {
- assertThat(underTest.activeMobileDataSubscriptionId.value)
- .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
- }
-
- @Test
- fun testActiveDataSubscriptionId_updates() =
- runBlocking(IMMEDIATE) {
- var active: Int? = null
-
- val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
-
- getActiveDataSubscriptionCallback().onActiveDataSubscriptionIdChanged(SUB_2_ID)
-
- assertThat(active).isEqualTo(SUB_2_ID)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_default() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isEqualTo(MobileSubscriptionModel())
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_emergencyOnly() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val serviceState = ServiceState()
- serviceState.isEmergencyOnly = true
-
- getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
-
- assertThat(latest?.isEmergencyOnly).isEqualTo(true)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_emergencyOnly_toggles() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val callback = getTelephonyCallbackForType<ServiceStateListener>()
- val serviceState = ServiceState()
- serviceState.isEmergencyOnly = true
- callback.onServiceStateChanged(serviceState)
- serviceState.isEmergencyOnly = false
- callback.onServiceStateChanged(serviceState)
-
- assertThat(latest?.isEmergencyOnly).isEqualTo(false)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_signalStrengths_levelsUpdate() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val callback = getTelephonyCallbackForType<SignalStrengthsListener>()
- val strength = signalStrength(1, 2, true)
- callback.onSignalStrengthsChanged(strength)
-
- assertThat(latest?.isGsm).isEqualTo(true)
- assertThat(latest?.primaryLevel).isEqualTo(1)
- assertThat(latest?.cdmaLevel).isEqualTo(2)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_dataConnectionState() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val callback = getTelephonyCallbackForType<DataConnectionStateListener>()
- callback.onDataConnectionStateChanged(100, 200 /* unused */)
-
- assertThat(latest?.dataConnectionState).isEqualTo(100)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_dataActivity() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val callback = getTelephonyCallbackForType<DataActivityListener>()
- callback.onDataActivity(3)
-
- assertThat(latest?.dataActivityDirection).isEqualTo(3)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_carrierNetworkChange() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
- callback.onCarrierNetworkChange(true)
-
- assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_displayInfo() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- val callback = getTelephonyCallbackForType<DisplayInfoListener>()
- val ti = mock<TelephonyDisplayInfo>()
- callback.onDisplayInfoChanged(ti)
-
- assertThat(latest?.displayInfo).isEqualTo(ti)
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_isCached() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- val state1 = underTest.getFlowForSubId(SUB_1_ID)
- val state2 = underTest.getFlowForSubId(SUB_1_ID)
-
- assertThat(state1).isEqualTo(state2)
- }
-
- @Test
- fun testFlowForSubId_isRemovedAfterFinish() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
- var latest: MobileSubscriptionModel? = null
-
- // Start collecting on some flow
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- // There should be once cached flow now
- assertThat(underTest.getSubIdFlowCache().size).isEqualTo(1)
-
- // When the job is canceled, the cache should be cleared
- job.cancel()
-
- assertThat(underTest.getSubIdFlowCache().size).isEqualTo(0)
- }
-
- private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
- val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
- verify(subscriptionManager)
- .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun getActiveDataSubscriptionCallback(): ActiveDataSubscriptionIdListener =
- getTelephonyCallbackForType()
-
- private fun getTelephonyCallbacks(): List<TelephonyCallback> {
- val callbackCaptor = argumentCaptor<TelephonyCallback>()
- verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
- return callbackCaptor.allValues
- }
-
- private inline fun <reified T> getTelephonyCallbackForType(): T {
- val cbs = getTelephonyCallbacks().filterIsInstance<T>()
- assertThat(cbs.size).isEqualTo(1)
- return cbs[0]
- }
-
- /** Convenience constructor for SignalStrength */
- private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
- val signalStrength = mock<SignalStrength>()
- whenever(signalStrength.isGsm).thenReturn(isGsm)
- whenever(signalStrength.level).thenReturn(gsmLevel)
- val cdmaStrength =
- mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
- whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
- .thenReturn(listOf(cdmaStrength))
-
- return signalStrength
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- private const val SUB_1_ID = 1
- private val SUB_1 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
-
- private const val SUB_2_ID = 2
- private val SUB_2 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 8ec68f3..cd4dbeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -23,7 +23,7 @@
class FakeMobileIconInteractor : MobileIconInteractor {
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
- override val iconGroup = _iconGroup
+ override val networkTypeIconGroup = _iconGroup
private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
override val isEmergencyOnly = _isEmergencyOnly
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
new file mode 100644
index 0000000..2bd2286
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
+import android.telephony.TelephonyManager.NETWORK_TYPE_GSM
+import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) :
+ MobileIconsInteractor {
+ val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
+ val LTE_KEY = mobileMappings.toIconKey(LTE)
+ val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
+ val FIVE_G_OVERRIDE_KEY = mobileMappings.toIconKeyOverride(FIVE_G_OVERRIDE)
+
+ /**
+ * To avoid a reliance on [MobileMappings], we'll build a simpler map from network type to
+ * mobile icon. See TelephonyManager.NETWORK_TYPES for a list of types and [TelephonyIcons] for
+ * the exhaustive set of icons
+ */
+ val TEST_MAPPING: Map<String, MobileIconGroup> =
+ mapOf(
+ THREE_G_KEY to TelephonyIcons.THREE_G,
+ LTE_KEY to TelephonyIcons.LTE,
+ FOUR_G_KEY to TelephonyIcons.FOUR_G,
+ FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
+ )
+
+ private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
+ override val filteredSubscriptions = _filteredSubscriptions
+
+ private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
+ override val defaultMobileIconMapping = _defaultMobileIconMapping
+
+ private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
+ override val defaultMobileIconGroup = _defaultMobileIconGroup
+
+ private val _isUserSetup = MutableStateFlow(true)
+ override val isUserSetup = _isUserSetup
+
+ /** Always returns a new fake interactor */
+ override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
+ return FakeMobileIconInteractor()
+ }
+
+ companion object {
+ val DEFAULT_ICON = TelephonyIcons.G
+
+ // Use [MobileMappings] to define some simple definitions
+ const val THREE_G = NETWORK_TYPE_GSM
+ const val LTE = NETWORK_TYPE_LTE
+ const val FOUR_G = NETWORK_TYPE_UMTS
+ const val FIVE_G_OVERRIDE = OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 2f07d9c..ff44af4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -18,10 +18,19 @@
import android.telephony.CellSignalStrength
import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -29,26 +38,33 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
@SmallTest
class MobileIconInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconInteractor
- private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository()
- private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID)
+ private val mobileMappingsProxy = FakeMobileMappingsProxy()
+ private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
+ private val connectionRepository = FakeMobileConnectionRepository()
@Before
fun setUp() {
- underTest = MobileIconInteractorImpl(sub1Flow)
+ underTest =
+ MobileIconInteractorImpl(
+ mobileIconsInteractor.defaultMobileIconMapping,
+ mobileIconsInteractor.defaultMobileIconGroup,
+ mobileMappingsProxy,
+ connectionRepository,
+ )
}
@Test
fun gsm_level_default_unknown() =
runBlocking(IMMEDIATE) {
- mobileSubscriptionRepository.setMobileSubscriptionModel(
+ connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(isGsm = true),
- SUB_1_ID
)
var latest: Int? = null
@@ -62,13 +78,12 @@
@Test
fun gsm_usesGsmLevel() =
runBlocking(IMMEDIATE) {
- mobileSubscriptionRepository.setMobileSubscriptionModel(
+ connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(
isGsm = true,
primaryLevel = GSM_LEVEL,
cdmaLevel = CDMA_LEVEL
),
- SUB_1_ID
)
var latest: Int? = null
@@ -82,9 +97,8 @@
@Test
fun cdma_level_default_unknown() =
runBlocking(IMMEDIATE) {
- mobileSubscriptionRepository.setMobileSubscriptionModel(
+ connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(isGsm = false),
- SUB_1_ID
)
var latest: Int? = null
@@ -97,13 +111,12 @@
@Test
fun cdma_usesCdmaLevel() =
runBlocking(IMMEDIATE) {
- mobileSubscriptionRepository.setMobileSubscriptionModel(
+ connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(
isGsm = false,
primaryLevel = GSM_LEVEL,
cdmaLevel = CDMA_LEVEL
),
- SUB_1_ID
)
var latest: Int? = null
@@ -114,6 +127,75 @@
job.cancel()
}
+ @Test
+ fun iconGroup_three_g() =
+ runBlocking(IMMEDIATE) {
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
+ )
+
+ var latest: MobileIconGroup? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(TelephonyIcons.THREE_G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_updates_on_change() =
+ runBlocking(IMMEDIATE) {
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
+ )
+
+ var latest: MobileIconGroup? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ resolvedNetworkType = DefaultNetworkType(FOUR_G),
+ ),
+ )
+ yield()
+
+ assertThat(latest).isEqualTo(TelephonyIcons.FOUR_G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_5g_override_type() =
+ runBlocking(IMMEDIATE) {
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(resolvedNetworkType = OverrideNetworkType(FIVE_G_OVERRIDE)),
+ )
+
+ var latest: MobileIconGroup? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(TelephonyIcons.NR_5G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_default_if_no_lookup() =
+ runBlocking(IMMEDIATE) {
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ resolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
+ ),
+ )
+
+ var latest: MobileIconGroup? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(FakeMobileIconsInteractor.DEFAULT_ICON)
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
@@ -123,9 +205,5 @@
private const val SUB_1_ID = 1
private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
-
- private const val SUB_2_ID = 2
- private val SUB_2 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 89ad9cb..b01efd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -19,12 +19,14 @@
import android.telephony.SubscriptionInfo
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -39,7 +41,9 @@
class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor
private val userSetupRepository = FakeUserSetupRepository()
- private val subscriptionsRepository = FakeMobileSubscriptionRepository()
+ private val subscriptionsRepository = FakeMobileConnectionsRepository()
+ private val mobileMappingsProxy = FakeMobileMappingsProxy()
+ private val scope = CoroutineScope(IMMEDIATE)
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@@ -47,10 +51,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest =
- MobileIconsInteractor(
+ MobileIconsInteractorImpl(
subscriptionsRepository,
carrierConfigTracker,
+ mobileMappingsProxy,
userSetupRepository,
+ scope
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
new file mode 100644
index 0000000..6d8d902
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.util
+
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.settingslib.mobile.TelephonyIcons
+
+class FakeMobileMappingsProxy : MobileMappingsProxy {
+ private var iconMap = mapOf<String, MobileIconGroup>()
+ private var defaultIcons = TelephonyIcons.THREE_G
+
+ fun setIconMap(map: Map<String, MobileIconGroup>) {
+ iconMap = map
+ }
+ override fun mapIconSets(config: Config): Map<String, MobileIconGroup> = iconMap
+ fun getIconMap() = iconMap
+
+ fun setDefaultIcons(group: MobileIconGroup) {
+ defaultIcons = group
+ }
+ override fun getDefaultIcons(config: Config): MobileIconGroup = defaultIcons
+ fun getDefaultIcons(): MobileIconGroup = defaultIcons
+
+ override fun toIconKey(networkType: Int): String {
+ return networkType.toString()
+ }
+
+ override fun toIconKeyOverride(networkType: Int): String {
+ return toIconKey(networkType) + "_override"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index e18dd3a..7d5f06c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -140,6 +140,40 @@
}
@Test
+ fun testOnUnfold_hingeAngleDecreasesBeforeInnerScreenAvailable_emitsOnlyStartAndInnerScreenAvailableEvents() {
+ setFoldState(folded = true)
+ foldUpdates.clear()
+
+ setFoldState(folded = false)
+ screenOnStatusProvider.notifyScreenTurningOn()
+ sendHingeAngleEvent(10)
+ sendHingeAngleEvent(20)
+ sendHingeAngleEvent(10)
+ screenOnStatusProvider.notifyScreenTurnedOn()
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING,
+ FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE)
+ }
+
+ @Test
+ fun testOnUnfold_hingeAngleDecreasesAfterInnerScreenAvailable_emitsStartInnerScreenAvailableAndStartClosingEvents() {
+ setFoldState(folded = true)
+ foldUpdates.clear()
+
+ setFoldState(folded = false)
+ screenOnStatusProvider.notifyScreenTurningOn()
+ sendHingeAngleEvent(10)
+ sendHingeAngleEvent(20)
+ screenOnStatusProvider.notifyScreenTurnedOn()
+ sendHingeAngleEvent(30)
+ sendHingeAngleEvent(40)
+ sendHingeAngleEvent(10)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING,
+ FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, FOLD_UPDATE_START_CLOSING)
+ }
+
+ @Test
fun testOnFolded_stopsHingeAngleProvider() {
setFoldState(folded = true)
@@ -237,7 +271,7 @@
}
@Test
- fun startClosingEvent_afterTimeout_abortEmitted() {
+ fun startClosingEvent_afterTimeout_finishHalfOpenEventEmitted() {
sendHingeAngleEvent(90)
sendHingeAngleEvent(80)
@@ -269,7 +303,7 @@
}
@Test
- fun startClosingEvent_timeoutAfterTimeoutRescheduled_abortEmitted() {
+ fun startClosingEvent_timeoutAfterTimeoutRescheduled_finishHalfOpenStateEmitted() {
sendHingeAngleEvent(180)
sendHingeAngleEvent(90)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractorTest.java
similarity index 77%
rename from packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractorTest.java
index 76bff1d..7e8ffeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractorTest.java
@@ -54,7 +54,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class WallpaperColorExtractorTest extends SysuiTestCase {
+public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
private static final int LOW_BMP_WIDTH = 128;
private static final int LOW_BMP_HEIGHT = 128;
private static final int HIGH_BMP_WIDTH = 3000;
@@ -105,11 +105,11 @@
return bitmap;
}
- private WallpaperColorExtractor getSpyWallpaperColorExtractor() {
+ private WallpaperLocalColorExtractor getSpyWallpaperLocalColorExtractor() {
- WallpaperColorExtractor wallpaperColorExtractor = new WallpaperColorExtractor(
+ WallpaperLocalColorExtractor colorExtractor = new WallpaperLocalColorExtractor(
mBackgroundExecutor,
- new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
@Override
public void onColorsProcessed(List<RectF> regions,
List<WallpaperColors> colors) {
@@ -132,25 +132,25 @@
mDeactivatedCount++;
}
});
- WallpaperColorExtractor spyWallpaperColorExtractor = spy(wallpaperColorExtractor);
+ WallpaperLocalColorExtractor spyColorExtractor = spy(colorExtractor);
doAnswer(invocation -> {
mMiniBitmapWidth = invocation.getArgument(1);
mMiniBitmapHeight = invocation.getArgument(2);
return getMockBitmap(mMiniBitmapWidth, mMiniBitmapHeight);
- }).when(spyWallpaperColorExtractor).createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+ }).when(spyColorExtractor).createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
doAnswer(invocation -> getMockBitmap(
invocation.getArgument(1),
invocation.getArgument(2)))
- .when(spyWallpaperColorExtractor)
+ .when(spyColorExtractor)
.createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
- .when(spyWallpaperColorExtractor).getLocalWallpaperColors(any(Rect.class));
+ .when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class));
- return spyWallpaperColorExtractor;
+ return spyColorExtractor;
}
private RectF randomArea() {
@@ -180,18 +180,18 @@
*/
@Test
public void testMiniBitmapCreation() {
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
int nSimulations = 10;
for (int i = 0; i < nSimulations; i++) {
resetCounters();
int width = randomBetween(LOW_BMP_WIDTH, HIGH_BMP_WIDTH);
int height = randomBetween(LOW_BMP_HEIGHT, HIGH_BMP_HEIGHT);
Bitmap bitmap = getMockBitmap(width, height);
- spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ spyColorExtractor.onBitmapChanged(bitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
assertThat(Math.min(mMiniBitmapWidth, mMiniBitmapHeight))
- .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ .isAtMost(WallpaperLocalColorExtractor.SMALL_SIDE);
}
}
@@ -201,18 +201,18 @@
*/
@Test
public void testSmallMiniBitmapCreation() {
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
int nSimulations = 10;
for (int i = 0; i < nSimulations; i++) {
resetCounters();
int width = randomBetween(VERY_LOW_BMP_WIDTH, LOW_BMP_WIDTH);
int height = randomBetween(VERY_LOW_BMP_HEIGHT, LOW_BMP_HEIGHT);
Bitmap bitmap = getMockBitmap(width, height);
- spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ spyColorExtractor.onBitmapChanged(bitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
assertThat(Math.max(mMiniBitmapWidth, mMiniBitmapHeight))
- .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ .isAtMost(WallpaperLocalColorExtractor.SMALL_SIDE);
}
}
@@ -228,15 +228,15 @@
int nSimulations = 10;
for (int i = 0; i < nSimulations; i++) {
resetCounters();
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
List<RectF> regions = listOfRandomAreas(MIN_AREAS, MAX_AREAS);
int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
List<Runnable> tasks = Arrays.asList(
- () -> spyWallpaperColorExtractor.onPageChanged(nPages),
- () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
- () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ () -> spyColorExtractor.onPageChanged(nPages),
+ () -> spyColorExtractor.onBitmapChanged(bitmap),
+ () -> spyColorExtractor.setDisplayDimensions(
DISPLAY_WIDTH, DISPLAY_HEIGHT),
- () -> spyWallpaperColorExtractor.addLocalColorsAreas(
+ () -> spyColorExtractor.addLocalColorsAreas(
regions));
Collections.shuffle(tasks);
tasks.forEach(Runnable::run);
@@ -245,7 +245,7 @@
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
assertThat(mColorsProcessed).isEqualTo(regions.size());
- spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ spyColorExtractor.removeLocalColorAreas(regions);
assertThat(mDeactivatedCount).isEqualTo(1);
}
}
@@ -260,7 +260,7 @@
int nSimulations = 10;
for (int i = 0; i < nSimulations; i++) {
resetCounters();
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
List<RectF> regions = new ArrayList<>();
@@ -268,20 +268,20 @@
regions.addAll(regions2);
int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
List<Runnable> tasks = Arrays.asList(
- () -> spyWallpaperColorExtractor.onPageChanged(nPages),
- () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
- () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ () -> spyColorExtractor.onPageChanged(nPages),
+ () -> spyColorExtractor.onBitmapChanged(bitmap),
+ () -> spyColorExtractor.setDisplayDimensions(
DISPLAY_WIDTH, DISPLAY_HEIGHT),
- () -> spyWallpaperColorExtractor.removeLocalColorAreas(regions1));
+ () -> spyColorExtractor.removeLocalColorAreas(regions1));
- spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ spyColorExtractor.addLocalColorsAreas(regions);
assertThat(mActivatedCount).isEqualTo(1);
Collections.shuffle(tasks);
tasks.forEach(Runnable::run);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
assertThat(mDeactivatedCount).isEqualTo(0);
- spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ spyColorExtractor.removeLocalColorAreas(regions2);
assertThat(mDeactivatedCount).isEqualTo(1);
}
}
@@ -295,18 +295,18 @@
@Test
public void testRecomputeColorExtraction() {
Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
List<RectF> regions = new ArrayList<>();
regions.addAll(regions1);
regions.addAll(regions2);
- spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ spyColorExtractor.addLocalColorsAreas(regions);
assertThat(mActivatedCount).isEqualTo(1);
int nPages = PAGES_LOW;
- spyWallpaperColorExtractor.onBitmapChanged(bitmap);
- spyWallpaperColorExtractor.onPageChanged(nPages);
- spyWallpaperColorExtractor.setDisplayDimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT);
+ spyColorExtractor.onBitmapChanged(bitmap);
+ spyColorExtractor.onPageChanged(nPages);
+ spyColorExtractor.setDisplayDimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT);
int nSimulations = 20;
for (int i = 0; i < nSimulations; i++) {
@@ -315,22 +315,22 @@
// verify that if we remove some regions, they are not recomputed after other changes
if (i == nSimulations / 2) {
regions.removeAll(regions2);
- spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ spyColorExtractor.removeLocalColorAreas(regions2);
}
if (Math.random() >= 0.5) {
int nPagesNew = randomBetween(PAGES_LOW, PAGES_HIGH);
if (nPagesNew == nPages) continue;
nPages = nPagesNew;
- spyWallpaperColorExtractor.onPageChanged(nPagesNew);
+ spyColorExtractor.onPageChanged(nPagesNew);
} else {
Bitmap newBitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
- spyWallpaperColorExtractor.onBitmapChanged(newBitmap);
+ spyColorExtractor.onBitmapChanged(newBitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
}
assertThat(mColorsProcessed).isEqualTo(regions.size());
}
- spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ spyColorExtractor.removeLocalColorAreas(regions);
assertThat(mDeactivatedCount).isEqualTo(1);
}
@@ -339,12 +339,12 @@
resetCounters();
Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
doNothing().when(bitmap).recycle();
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
- spyWallpaperColorExtractor.onPageChanged(PAGES_LOW);
- spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onPageChanged(PAGES_LOW);
+ spyColorExtractor.onBitmapChanged(bitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
- spyWallpaperColorExtractor.cleanUp();
- spyWallpaperColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
+ spyColorExtractor.cleanUp();
+ spyColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
assertThat(mColorsProcessed).isEqualTo(0);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 7af66f6..7ae47b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.notetask.NoteTaskInitializer;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -36,7 +37,6 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -78,18 +78,31 @@
@Mock ProtoTracer mProtoTracer;
@Mock UserTracker mUserTracker;
@Mock ShellExecutor mSysUiMainExecutor;
- @Mock FloatingTasks mFloatingTasks;
+ @Mock NoteTaskInitializer mNoteTaskInitializer;
@Mock DesktopMode mDesktopMode;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
- Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mFloatingTasks),
+ mWMShell = new WMShell(
+ mContext,
+ mShellInterface,
+ Optional.of(mPip),
+ Optional.of(mSplitScreen),
+ Optional.of(mOneHanded),
Optional.of(mDesktopMode),
- mCommandQueue, mConfigurationController, mKeyguardStateController,
- mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer,
- mWakefulnessLifecycle, mUserTracker, mSysUiMainExecutor);
+ mCommandQueue,
+ mConfigurationController,
+ mKeyguardStateController,
+ mKeyguardUpdateMonitor,
+ mScreenLifecycle,
+ mSysUiState,
+ mProtoTracer,
+ mWakefulnessLifecycle,
+ mUserTracker,
+ mNoteTaskInitializer,
+ mSysUiMainExecutor
+ );
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
new file mode 100644
index 0000000..96658c6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -0,0 +1,48 @@
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.biometrics.PromptInfo
+import com.android.systemui.biometrics.data.model.PromptKind
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [PromptRepository] for tests. */
+class FakePromptRepository : PromptRepository {
+
+ private val _isShowing = MutableStateFlow(false)
+ override val isShowing = _isShowing.asStateFlow()
+
+ private val _promptInfo = MutableStateFlow<PromptInfo?>(null)
+ override val promptInfo = _promptInfo.asStateFlow()
+
+ private val _userId = MutableStateFlow<Int?>(null)
+ override val userId = _userId.asStateFlow()
+
+ private var _challenge = MutableStateFlow<Long?>(null)
+ override val challenge = _challenge.asStateFlow()
+
+ private val _kind = MutableStateFlow(PromptKind.ANY_BIOMETRIC)
+ override val kind = _kind.asStateFlow()
+
+ override fun setPrompt(
+ promptInfo: PromptInfo,
+ userId: Int,
+ gatekeeperChallenge: Long?,
+ kind: PromptKind
+ ) {
+ _promptInfo.value = promptInfo
+ _userId.value = userId
+ _challenge.value = gatekeeperChallenge
+ _kind.value = kind
+ }
+
+ override fun unsetPrompt() {
+ _promptInfo.value = null
+ _userId.value = null
+ _challenge.value = null
+ _kind.value = PromptKind.ANY_BIOMETRIC
+ }
+
+ fun setIsShowing(showing: Boolean) {
+ _isShowing.value = showing
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt
new file mode 100644
index 0000000..fbe291e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt
@@ -0,0 +1,31 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import com.android.internal.widget.LockscreenCredential
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Fake implementation of [CredentialInteractor] for tests. */
+class FakeCredentialInteractor : CredentialInteractor {
+
+ /** Sets return value for [isStealthModeActive]. */
+ var stealthMode: Boolean = false
+
+ /** Sets return value for [getCredentialOwnerOrSelfId]. */
+ var credentialOwnerId: Int? = null
+
+ override fun isStealthModeActive(userId: Int): Boolean = stealthMode
+
+ override fun getCredentialOwnerOrSelfId(userId: Int): Int = credentialOwnerId ?: userId
+
+ override fun verifyCredential(
+ request: BiometricPromptRequest.Credential,
+ credential: LockscreenCredential,
+ ): Flow<CredentialStatus> = verifyCredentialResponse(credential)
+
+ /** Sets the result value for [verifyCredential]. */
+ var verifyCredentialResponse: (credential: LockscreenCredential) -> Flow<CredentialStatus> =
+ { _ ->
+ flowOf(CredentialStatus.Fail.Error("invalid"))
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index 8d171be..69575a9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -26,7 +26,9 @@
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatcher
import org.mockito.Mockito
+import org.mockito.Mockito.`when`
import org.mockito.stubbing.OngoingStubbing
+import org.mockito.stubbing.Stubber
/**
* Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
@@ -89,7 +91,8 @@
*
* @see Mockito.when
*/
-fun <T> whenever(methodCall: T): OngoingStubbing<T> = Mockito.`when`(methodCall)
+fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
+fun <T> Stubber.whenever(mock: T): T = `when`(mock)
/**
* A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 043aff6..b568186 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.unfold.progress
+import android.os.Trace
import android.util.Log
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -117,6 +118,7 @@
if (DEBUG) {
Log.d(TAG, "onFoldUpdate = $update")
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "fold_update", update)
}
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 07473b3..808128d 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -16,6 +16,7 @@
package com.android.systemui.unfold.updates
import android.os.Handler
+import android.os.Trace
import android.util.Log
import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
@@ -108,6 +109,7 @@
private fun onHingeAngle(angle: Float) {
if (DEBUG) {
Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle")
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "hinge_angle", angle.toInt())
}
val isClosing = angle < lastHingeAngle
@@ -115,8 +117,16 @@
val closingThresholdMet = closingThreshold == null || angle < closingThreshold
val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+ val screenAvailableEventSent = isUnfoldHandled
- if (isClosing && closingThresholdMet && !closingEventDispatched && !isFullyOpened) {
+ if (isClosing // hinge angle should be decreasing since last update
+ && closingThresholdMet // hinge angle is below certain threshold
+ && !closingEventDispatched // we haven't sent closing event already
+ && !isFullyOpened // do not send closing event if we are in fully opened hinge
+ // angle range as closing threshold could overlap this range
+ && screenAvailableEventSent // do not send closing event if we are still in
+ // the process of turning on the inner display
+ ) {
notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
}
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 3d24588..9897a07 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.graphics.Camera;
import android.graphics.GraphicBuffer;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
@@ -124,26 +123,31 @@
private static final String CAMERA_EXTENSION_VERSION_NAME =
"androidx.camera.extensions.impl.ExtensionVersionImpl";
- private static final String LATEST_VERSION = "1.3.0";
+ private static final String LATEST_VERSION = "1.4.0";
// No support for the init sequence
private static final String NON_INIT_VERSION_PREFIX = "1.0";
// Support advanced API and latency queries
private static final String ADVANCED_VERSION_PREFIX = "1.2";
// Support for the capture request & result APIs
private static final String RESULTS_VERSION_PREFIX = "1.3";
- private static final String[] ADVANCED_VERSION_PREFIXES = {ADVANCED_VERSION_PREFIX,
- RESULTS_VERSION_PREFIX};
- private static final String[] SUPPORTED_VERSION_PREFIXES = {RESULTS_VERSION_PREFIX,
- ADVANCED_VERSION_PREFIX, "1.1", NON_INIT_VERSION_PREFIX};
+ // Support for various latency improvements
+ private static final String LATENCY_VERSION_PREFIX = "1.4";
+ private static final String[] ADVANCED_VERSION_PREFIXES = {LATENCY_VERSION_PREFIX,
+ ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX };
+ private static final String[] SUPPORTED_VERSION_PREFIXES = {LATENCY_VERSION_PREFIX,
+ RESULTS_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, "1.1", NON_INIT_VERSION_PREFIX};
private static final boolean EXTENSIONS_PRESENT = checkForExtensions();
private static final String EXTENSIONS_VERSION = EXTENSIONS_PRESENT ?
(new ExtensionVersionImpl()).checkApiVersion(LATEST_VERSION) : null;
private static final boolean LATENCY_API_SUPPORTED = checkForLatencyAPI();
+ private static final boolean LATENCY_IMPROVEMENTS_SUPPORTED = EXTENSIONS_PRESENT &&
+ (EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX));
private static final boolean ADVANCED_API_SUPPORTED = checkForAdvancedAPI();
private static final boolean INIT_API_SUPPORTED = EXTENSIONS_PRESENT &&
(!EXTENSIONS_VERSION.startsWith(NON_INIT_VERSION_PREFIX));
private static final boolean RESULT_API_SUPPORTED = EXTENSIONS_PRESENT &&
- (EXTENSIONS_VERSION.startsWith(RESULTS_VERSION_PREFIX));
+ (EXTENSIONS_VERSION.startsWith(RESULTS_VERSION_PREFIX) ||
+ EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX));
private HashMap<String, CameraCharacteristics> mCharacteristicsHashMap = new HashMap<>();
private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
@@ -1169,6 +1173,10 @@
ret.outputConfigs.add(entry);
}
ret.sessionTemplateId = sessionConfig.getSessionTemplateId();
+ ret.sessionType = -1;
+ if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ ret.sessionType = sessionConfig.getSessionType();
+ }
ret.sessionParameter = initializeParcelableMetadata(
sessionConfig.getSessionParameters(), cameraId);
mCameraId = cameraId;
@@ -1312,6 +1320,15 @@
}
@Override
+ public int getSessionType() {
+ if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ return mPreviewExtender.onSessionType();
+ }
+
+ return -1;
+ }
+
+ @Override
public int getProcessorType() {
ProcessorType processorType = mPreviewExtender.getProcessorType();
if (processorType == ProcessorType.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY) {
@@ -1407,6 +1424,15 @@
}
@Override
+ public int getSessionType() {
+ if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ return mImageExtender.onSessionType();
+ }
+
+ return -1;
+ }
+
+ @Override
public void init(String cameraId, CameraMetadataNative chars) {
CameraCharacteristics c = new CameraCharacteristics(chars);
mCameraManager.registerDeviceStateListener(c);
diff --git a/proto/src/task_snapshot.proto b/proto/src/task_snapshot.proto
deleted file mode 100644
index 1cbc17e..0000000
--- a/proto/src/task_snapshot.proto
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- syntax = "proto3";
-
- package com.android.server.wm;
-
- option java_package = "com.android.server.wm";
- option java_outer_classname = "WindowManagerProtos";
-
- message TaskSnapshotProto {
- int32 orientation = 1;
- int32 inset_left = 2;
- int32 inset_top = 3;
- int32 inset_right = 4;
- int32 inset_bottom = 5;
- bool is_real_snapshot = 6;
- int32 windowing_mode = 7;
- int32 system_ui_visibility = 8 [deprecated=true];
- bool is_translucent = 9;
- string top_activity_component = 10;
- // deprecated because original width and height are stored now instead of the scale.
- float legacy_scale = 11 [deprecated=true];
- int64 id = 12;
- int32 rotation = 13;
- // The task width when the snapshot was taken
- int32 task_width = 14;
- // The task height when the snapshot was taken
- int32 task_height = 15;
- int32 appearance = 16;
- int32 letterbox_inset_left = 17;
- int32 letterbox_inset_top = 18;
- int32 letterbox_inset_right = 19;
- int32 letterbox_inset_bottom = 20;
- }
diff --git a/proto/src/windowmanager.proto b/proto/src/windowmanager.proto
new file mode 100644
index 0000000..f26404c6
--- /dev/null
+++ b/proto/src/windowmanager.proto
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package com.android.server.wm;
+
+option java_package = "com.android.server.wm";
+option java_outer_classname = "WindowManagerProtos";
+
+message TaskSnapshotProto {
+ int32 orientation = 1;
+ int32 inset_left = 2;
+ int32 inset_top = 3;
+ int32 inset_right = 4;
+ int32 inset_bottom = 5;
+ bool is_real_snapshot = 6;
+ int32 windowing_mode = 7;
+ int32 system_ui_visibility = 8 [deprecated=true];
+ bool is_translucent = 9;
+ string top_activity_component = 10;
+ // deprecated because original width and height are stored now instead of the scale.
+ float legacy_scale = 11 [deprecated=true];
+ int64 id = 12;
+ int32 rotation = 13;
+ // The task width when the snapshot was taken
+ int32 task_width = 14;
+ // The task height when the snapshot was taken
+ int32 task_height = 15;
+ int32 appearance = 16;
+ int32 letterbox_inset_left = 17;
+ int32 letterbox_inset_top = 18;
+ int32 letterbox_inset_right = 19;
+ int32 letterbox_inset_bottom = 20;
+}
+
+// Persistent letterboxing configurations
+message LetterboxProto {
+
+ // Possible values for the letterbox horizontal reachability
+ enum LetterboxHorizontalReachability {
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT = 0;
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER = 1;
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT = 2;
+ }
+
+ // Possible values for the letterbox vertical reachability
+ enum LetterboxVerticalReachability {
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP = 0;
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER = 1;
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM = 2;
+ }
+
+ // Represents the current horizontal position for the letterboxed activity
+ LetterboxHorizontalReachability letterbox_position_for_horizontal_reachability = 1;
+ // Represents the current vertical position for the letterboxed activity
+ LetterboxVerticalReachability letterbox_position_for_vertical_reachability = 2;
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 1df382f..f35de17 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -176,6 +176,7 @@
private boolean mSendMotionEvents;
+ private SparseArray<Boolean> mServiceDetectsGestures = new SparseArray<>(0);
boolean mRequestFilterKeyEvents;
boolean mRetrieveInteractiveWindows;
@@ -2369,9 +2370,17 @@
}
public void setServiceDetectsGesturesEnabled(int displayId, boolean mode) {
+ mServiceDetectsGestures.put(displayId, mode);
mSystemSupport.setServiceDetectsGesturesEnabled(displayId, mode);
}
+ public boolean isServiceDetectsGesturesEnabled(int displayId) {
+ if (mServiceDetectsGestures.contains(displayId)) {
+ return mServiceDetectsGestures.get(displayId);
+ }
+ return false;
+ }
+
public void requestTouchExploration(int displayId) {
mSystemSupport.requestTouchExploration(displayId);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 75724bf..d80117d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -176,6 +176,8 @@
private int mEnabledFeatures;
+ // Display-specific features
+ private SparseArray<Boolean> mServiceDetectsGestures = new SparseArray<>();
private final SparseArray<EventStreamState> mMouseStreamStates = new SparseArray<>(0);
private final SparseArray<EventStreamState> mTouchScreenStreamStates = new SparseArray<>(0);
@@ -458,7 +460,9 @@
final Context displayContext = mContext.createDisplayContext(display);
final int displayId = display.getDisplayId();
-
+ if (!mServiceDetectsGestures.contains(displayId)) {
+ mServiceDetectsGestures.put(displayId, false);
+ }
if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
if (mAutoclickController == null) {
mAutoclickController = new AutoclickController(
@@ -481,6 +485,7 @@
if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) {
explorer.setSendMotionEventsEnabled(true);
}
+ explorer.setServiceDetectsGestures(mServiceDetectsGestures.get(displayId));
addFirstEventHandler(displayId, explorer);
mTouchExplorer.put(displayId, explorer);
}
@@ -897,6 +902,11 @@
if (mTouchExplorer.contains(displayId)) {
mTouchExplorer.get(displayId).setServiceDetectsGestures(mode);
}
+ mServiceDetectsGestures.put(displayId, mode);
+ }
+
+ public void resetServiceDetectsGestures() {
+ mServiceDetectsGestures.clear();
}
public void requestTouchExploration(int displayId) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 085a589..47b4156 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1695,31 +1695,34 @@
}
private boolean scheduleNotifyMotionEvent(MotionEvent event) {
+ boolean result = false;
+ int displayId = event.getDisplayId();
synchronized (mLock) {
AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestTouchExplorationMode) {
+ if (service.isServiceDetectsGesturesEnabled(displayId)) {
service.notifyMotionEvent(event);
- return true;
+ result = true;
}
}
}
- return false;
+ return result;
}
private boolean scheduleNotifyTouchState(int displayId, int touchState) {
+ boolean result = false;
synchronized (mLock) {
AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestTouchExplorationMode) {
+ if (service.isServiceDetectsGesturesEnabled(displayId)) {
service.notifyTouchState(displayId, touchState);
- return true;
+ result = true;
}
}
}
- return false;
+ return result;
}
private void notifyClearAccessibilityCacheLocked() {
@@ -2292,8 +2295,9 @@
if (!mHasInputFilter) {
mHasInputFilter = true;
if (mInputFilter == null) {
- mInputFilter = new AccessibilityInputFilter(mContext,
- AccessibilityManagerService.this);
+ mInputFilter =
+ new AccessibilityInputFilter(
+ mContext, AccessibilityManagerService.this);
}
inputFilter = mInputFilter;
setInputFilter = true;
@@ -2303,6 +2307,17 @@
if (mHasInputFilter) {
mHasInputFilter = false;
mInputFilter.setUserAndEnabledFeatures(userState.mUserId, 0);
+ mInputFilter.resetServiceDetectsGestures();
+ if (userState.isTouchExplorationEnabledLocked()) {
+ // Service gesture detection is turned on and off on a per-display
+ // basis.
+ final ArrayList<Display> displays = getValidDisplayList();
+ for (Display display : displays) {
+ int displayId = display.getDisplayId();
+ boolean mode = userState.isServiceDetectsGesturesEnabled(displayId);
+ mInputFilter.setServiceDetectsGesturesEnabled(displayId, mode);
+ }
+ }
inputFilter = null;
setInputFilter = true;
}
@@ -2618,6 +2633,18 @@
Binder.restoreCallingIdentity(identity);
}
}
+ // Service gesture detection is turned on and off on a per-display
+ // basis.
+ userState.resetServiceDetectsGestures();
+ final ArrayList<Display> displays = getValidDisplayList();
+ for (AccessibilityServiceConnection service: userState.mBoundServices) {
+ for (Display display : displays) {
+ int displayId = display.getDisplayId();
+ if (service.isServiceDetectsGesturesEnabled(displayId)) {
+ userState.setServiceDetectsGesturesEnabled(displayId, true);
+ }
+ }
+ }
userState.setServiceHandlesDoubleTapLocked(serviceHandlesDoubleTapEnabled);
userState.setMultiFingerGesturesLocked(requestMultiFingerGestures);
userState.setTwoFingerPassthroughLocked(requestTwoFingerPassthrough);
@@ -4342,6 +4369,7 @@
private void setServiceDetectsGesturesInternal(int displayId, boolean mode) {
synchronized (mLock) {
+ getCurrentUserStateLocked().setServiceDetectsGesturesEnabled(displayId, mode);
if (mHasInputFilter && mInputFilter != null) {
mInputFilter.setServiceDetectsGesturesEnabled(displayId, mode);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0cb7209..0db169f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -44,6 +44,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
@@ -118,6 +119,7 @@
private boolean mRequestMultiFingerGestures;
private boolean mRequestTwoFingerPassthrough;
private boolean mSendMotionEventsEnabled;
+ private SparseArray<Boolean> mServiceDetectsGestures = new SparseArray<>(0);
private int mUserInteractiveUiTimeout;
private int mUserNonInteractiveUiTimeout;
private int mNonInteractiveUiTimeout = 0;
@@ -991,4 +993,19 @@
mFocusStrokeWidth = strokeWidth;
mFocusColor = color;
}
+
+ public void setServiceDetectsGesturesEnabled(int displayId, boolean mode) {
+ mServiceDetectsGestures.put(displayId, mode);
+ }
+
+ public void resetServiceDetectsGestures() {
+ mServiceDetectsGestures.clear();
+ }
+
+ public boolean isServiceDetectsGesturesEnabled(int displayId) {
+ if (mServiceDetectsGestures.contains(displayId)) {
+ return mServiceDetectsGestures.get(displayId);
+ }
+ return false;
+ }
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 346fc6c..3cfae60 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -104,8 +104,6 @@
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TypedValue;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -124,6 +122,8 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.IRemoteViewsFactory;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.WidgetBackupProvider;
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
index 92435d0..3ea1bcb 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
@@ -23,8 +23,9 @@
import android.os.Build;
import android.text.TextUtils;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import java.io.IOException;
import java.util.Objects;
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index eac98f2..f0492a8 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -376,6 +376,16 @@
return;
}
+ // In some cases there may not be a monitor passed in when creating this task. So, if we
+ // don't have one already we ask the transport for a monitor.
+ if (mMonitor == null) {
+ try {
+ mMonitor = transport.getBackupManagerMonitor();
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Failed to retrieve monitor from transport");
+ }
+ }
+
// Set up to send data to the transport
final int N = mPackages.size();
final byte[] buffer = new byte[8192];
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 03796ea..95cc289 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -21,6 +21,7 @@
import static com.android.server.backup.BackupManagerService.TAG;
import android.app.backup.BackupManager.OperationType;
+import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreSet;
import android.os.Handler;
import android.os.HandlerThread;
@@ -203,6 +204,14 @@
}
}
+ // Ask the transport for a monitor that will be used to relay log events back to it.
+ IBackupManagerMonitor monitor = null;
+ try {
+ monitor = transport.getBackupManagerMonitor();
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Failed to retrieve monitor from transport");
+ }
+
// At this point, we have started a new journal file, and the old
// file identity is being passed to the backup processing task.
// When it completes successfully, that old journal file will be
@@ -225,7 +234,7 @@
queue,
oldJournal,
/* observer */ null,
- /* monitor */ null,
+ monitor,
listener,
Collections.emptyList(),
/* userInitiated */ false,
diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
index 237a3fa..40d7cad 100644
--- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -18,10 +18,12 @@
import android.annotation.Nullable;
import android.app.backup.BackupTransport;
+import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
import android.content.Intent;
import android.content.pm.PackageInfo;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
@@ -363,6 +365,15 @@
}
/**
+ * See {@link IBackupTransport#getBackupManagerMonitor()}
+ */
+ public IBackupManagerMonitor getBackupManagerMonitor() throws RemoteException {
+ AndroidFuture<IBackupManagerMonitor> resultFuture = mTransportFutures.newFuture();
+ mTransportBinder.getBackupManagerMonitor(resultFuture);
+ return IBackupManagerMonitor.Stub.asInterface((IBinder) getFutureResult(resultFuture));
+ }
+
+ /**
* Allows the {@link TransportConnection} to notify this client
* if the underlying transport has become unusable. If that happens
* we want to cancel all active futures or callbacks.
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index c4f5766..a57f5a2 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -45,11 +45,11 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
index dbff628..2c5d582 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
@@ -35,12 +35,12 @@
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index c713a41..551ffff 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -38,8 +38,6 @@
import android.util.AtomicFile;
import android.util.EventLog;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
@@ -47,6 +45,8 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 960922e..92889c0 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -40,8 +40,6 @@
import android.util.LongArrayQueue;
import android.util.MathUtils;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -49,6 +47,8 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 1150b83..72876f6 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -131,8 +131,6 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -147,6 +145,8 @@
import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.Installer;
import com.android.server.pm.UserManagerInternal;
import com.android.server.storage.AppFuseBridge;
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 933d259..e40f001 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -473,6 +473,18 @@
}
/**
+ * The {@link UserManager#isUserVisible() user visibility} changed.
+ *
+ * <p>This callback is called before the user starts or is switched to (or after it stops), when
+ * its visibility changed because of that action.
+ *
+ * @hide
+ */
+ // NOTE: change visible to int if this method becomes a @SystemApi
+ public void onUserVisibilityChanged(@NonNull TargetUser user, boolean visible) {
+ }
+
+ /**
* Called when an existing user is stopping, for system services to finalize any per-user
* state they maintain for running users. This is called prior to sending the SHUTDOWN
* broadcast to the user; it is a good place to stop making use of any resources of that
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 1a8cf0b..83d86cd 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -75,13 +75,17 @@
// Constants used on onUser(...)
// NOTE: do not change their values, as they're used on Trace calls and changes might break
// performance tests that rely on them.
- private static final String USER_STARTING = "Start"; // Logged as onStartUser
- private static final String USER_UNLOCKING = "Unlocking"; // Logged as onUnlockingUser
- private static final String USER_UNLOCKED = "Unlocked"; // Logged as onUnlockedUser
- private static final String USER_SWITCHING = "Switch"; // Logged as onSwitchUser
- private static final String USER_STOPPING = "Stop"; // Logged as onStopUser
- private static final String USER_STOPPED = "Cleanup"; // Logged as onCleanupUser
- private static final String USER_COMPLETED_EVENT = "CompletedEvent"; // onCompletedEventUser
+ private static final String USER_STARTING = "Start"; // Logged as onUserStarting()
+ private static final String USER_UNLOCKING = "Unlocking"; // Logged as onUserUnlocking()
+ private static final String USER_UNLOCKED = "Unlocked"; // Logged as onUserUnlocked()
+ private static final String USER_SWITCHING = "Switch"; // Logged as onUserSwitching()
+ private static final String USER_STOPPING = "Stop"; // Logged as onUserStopping()
+ private static final String USER_STOPPED = "Cleanup"; // Logged as onUserStopped()
+ private static final String USER_COMPLETED_EVENT = "CompletedEvent"; // onUserCompletedEvent()
+ private static final String USER_VISIBLE = "Visible"; // Logged on onUserVisible() and
+ // onUserStarting() (when visible is true)
+ private static final String USER_INVISIBLE = "Invisible"; // Logged on onUserStopping()
+ // (when visibilityChanged is true)
// The default number of threads to use if lifecycle thread pool is enabled.
private static final int DEFAULT_MAX_USER_POOL_THREADS = 3;
@@ -350,18 +354,41 @@
/**
* Starts the given user.
*/
- public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
- EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
+ public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId,
+ boolean visible) {
+ EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId, visible ? 1 : 0);
final TargetUser targetUser = newTargetUser(userId);
synchronized (mTargetUsers) {
mTargetUsers.put(userId, targetUser);
}
+ if (visible) {
+ // Must send the user visiiblity change first, for 2 reasons:
+ // 1. Automotive need to update the user-zone mapping ASAP and it's one of the few
+ // services listening to this event (OTOH, there are manyy listeners to USER_STARTING
+ // and some can take a while to process it)
+ // 2. When a user is switched from bg to fg, the onUserVisibilityChanged() callback is
+ // called onUserSwitching(), so calling it before onUserStarting() make it more
+ // consistent with that
+ onUser(t, USER_VISIBLE, /* prevUser= */ null, targetUser);
+ }
onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
}
/**
+ * Updates the user visibility.
+ *
+ * <p><b>NOTE: </b>this method should only be called when a user that is already running become
+ * visible; if the user is starting visible, callers should call
+ * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead
+ */
+ public void onUserVisible(@UserIdInt int userId) {
+ EventLog.writeEvent(EventLogTags.SSM_USER_VISIBLE, userId);
+ onUser(USER_VISIBLE, userId);
+ }
+
+ /**
* Unlocks the given user.
*/
public void onUserUnlocking(@UserIdInt int userId) {
@@ -408,9 +435,12 @@
/**
* Stops the given user.
*/
- public void onUserStopping(@UserIdInt int userId) {
- EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId);
+ public void onUserStopping(@UserIdInt int userId, boolean visibilityChanged) {
+ EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId, visibilityChanged ? 1 : 0);
onUser(USER_STOPPING, userId);
+ if (visibilityChanged) {
+ onUser(USER_INVISIBLE, userId);
+ }
}
/**
@@ -456,13 +486,12 @@
TargetUser targetUser = getTargetUser(userId);
Preconditions.checkState(targetUser != null, "No TargetUser for " + userId);
- onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, /* prevUser= */ null,
- targetUser);
+ onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, /* prevUser= */ null, targetUser);
}
private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
@Nullable TargetUser prevUser, @NonNull TargetUser curUser) {
- onUser(t, onWhat, prevUser, curUser, /* completedEventType=*/ null);
+ onUser(t, onWhat, prevUser, curUser, /* completedEventType= */ null);
}
private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
@@ -534,6 +563,12 @@
threadPool.submit(getOnUserCompletedEventRunnable(
t, service, serviceName, curUser, completedEventType));
break;
+ case USER_VISIBLE:
+ service.onUserVisibilityChanged(curUser, /* visible= */ true);
+ break;
+ case USER_INVISIBLE:
+ service.onUserVisibilityChanged(curUser, /* visible= */ false);
+ break;
default:
throw new IllegalArgumentException(onWhat + " what?");
}
diff --git a/services/core/java/com/android/server/SystemUpdateManagerService.java b/services/core/java/com/android/server/SystemUpdateManagerService.java
index fcba9b5..811a780 100644
--- a/services/core/java/com/android/server/SystemUpdateManagerService.java
+++ b/services/core/java/com/android/server/SystemUpdateManagerService.java
@@ -38,12 +38,12 @@
import android.provider.Settings;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java b/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java
index 725bccf..12f2e10 100644
--- a/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java
+++ b/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java
@@ -27,8 +27,9 @@
import android.content.res.TypedArray;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java b/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
index b379b5d..3603dcd 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
@@ -29,13 +29,13 @@
import android.util.PackageUtils;
import android.util.Pair;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 04bd869..62a97dc 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -65,8 +65,6 @@
import android.util.AtomicFile;
import android.util.Base64;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.R;
@@ -75,6 +73,8 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.FgThread;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 82af12e..2cf3462 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3891,24 +3891,29 @@
finishForceStopPackageLocked(packageName, appInfo.uid);
}
}
- final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
- Uri.fromParts("package", packageName, null));
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
- | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- intent.putExtra(Intent.EXTRA_UID, (appInfo != null) ? appInfo.uid : -1);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId);
- final int[] visibilityAllowList =
- mPackageManagerInt.getVisibilityAllowList(packageName, resolvedUserId);
- if (isInstantApp) {
- intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
- broadcastIntentInPackage("android", null, SYSTEM_UID, uid, pid, intent,
- null, null, null, 0, null, null, permission.ACCESS_INSTANT_APPS,
- null, false, false, resolvedUserId, false, null,
- visibilityAllowList);
- } else {
- broadcastIntentInPackage("android", null, SYSTEM_UID, uid, pid, intent,
- null, null, null, 0, null, null, null, null, false, false,
- resolvedUserId, false, null, visibilityAllowList);
+
+ if (succeeded) {
+ final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
+ Uri.fromParts("package", packageName, null /* fragment */));
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(Intent.EXTRA_UID,
+ (appInfo != null) ? appInfo.uid : INVALID_UID);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId);
+ if (isInstantApp) {
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ }
+ final int[] visibilityAllowList = mPackageManagerInt.getVisibilityAllowList(
+ packageName, resolvedUserId);
+
+ broadcastIntentInPackage("android", null /* featureId */,
+ SYSTEM_UID, uid, pid, intent, null /* resolvedType */,
+ null /* resultToApp */, null /* resultTo */, 0 /* resultCode */,
+ null /* resultData */, null /* resultExtras */,
+ isInstantApp ? permission.ACCESS_INSTANT_APPS : null,
+ null /* bOptions */, false /* serialized */, false /* sticky */,
+ resolvedUserId, false /* allowBackgroundActivityStarts */,
+ null /* backgroundActivityStartsToken */, visibilityAllowList);
}
if (observer != null) {
@@ -8346,14 +8351,14 @@
mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
Integer.toString(currentUserId), currentUserId);
- // On Automotive, at this point the system user has already been started and unlocked,
- // and some of the tasks we do here have already been done. So skip those in that case.
- // TODO(b/132262830, b/203885241): this workdound shouldn't be necessary once we move the
- // headless-user start logic to UserManager-land
+ // On Automotive / Headless System User Mode, at this point the system user has already been
+ // started and unlocked, and some of the tasks we do here have already been done. So skip
+ // those in that case.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move the headless-user
+ // start logic to UserManager-land
final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
-
if (bootingSystemUser) {
- mSystemServiceManager.onUserStarting(t, currentUserId);
+ mUserController.onSystemUserStarting();
}
synchronized (this) {
@@ -13897,7 +13902,7 @@
if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
(sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
+ " ordered=" + ordered + " userid=" + userId);
- if ((resultTo != null) && !ordered) {
+ if ((resultTo != null) && !ordered && !mEnableModernQueue) {
Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
}
@@ -14459,10 +14464,12 @@
filterNonExportedComponents(intent, callingUid, registeredReceivers,
mPlatformCompat, callerPackage);
int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
- if (!ordered && NR > 0) {
+ if (!ordered && NR > 0 && !mEnableModernQueue) {
// If we are not serializing this broadcast, then send the
// registered receivers separately so they don't wait for the
- // components to be launched.
+ // components to be launched. We don't do this split for the modern
+ // queue because delivery to registered receivers isn't blocked
+ // behind manifest receivers.
if (isCallerSystem) {
checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
isProtectedBroadcast, registeredReceivers);
@@ -16895,6 +16902,11 @@
return mSystemReady;
}
+ @Override
+ public boolean isModernQueueEnabled() {
+ return mEnableModernQueue;
+ }
+
/**
* Returns package name by pid.
*/
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index e0690bf..ba1c3b3 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -146,8 +146,6 @@
import android.util.SparseArray;
import android.util.SparseArrayMap;
import android.util.TimeUtils;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
@@ -157,6 +155,8 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.function.TriConsumer;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.AppStateTracker;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 28a81e6..417a0e5 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -133,7 +133,7 @@
*/
public boolean MODERN_QUEUE_ENABLED = DEFAULT_MODERN_QUEUE_ENABLED;
private static final String KEY_MODERN_QUEUE_ENABLED = "modern_queue_enabled";
- private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = false;
+ private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = true;
/**
* For {@link BroadcastQueueModernImpl}: Maximum number of process queues to
@@ -167,7 +167,7 @@
*/
public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS;
private static final String KEY_DELAY_NORMAL_MILLIS = "bcast_delay_normal_millis";
- private static final long DEFAULT_DELAY_NORMAL_MILLIS = 1_000;
+ private static final long DEFAULT_DELAY_NORMAL_MILLIS = 0;
/**
* For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts
@@ -175,7 +175,16 @@
*/
public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS;
private static final String KEY_DELAY_CACHED_MILLIS = "bcast_delay_cached_millis";
- private static final long DEFAULT_DELAY_CACHED_MILLIS = 10_000;
+ private static final long DEFAULT_DELAY_CACHED_MILLIS = 0;
+
+ /**
+ * For {@link BroadcastQueueModernImpl}: Delay to apply to urgent
+ * broadcasts, typically a negative value to indicate they should be
+ * executed before most other pending broadcasts.
+ */
+ public long DELAY_URGENT_MILLIS = DEFAULT_DELAY_URGENT_MILLIS;
+ private static final String KEY_DELAY_URGENT_MILLIS = "bcast_delay_urgent_millis";
+ private static final long DEFAULT_DELAY_URGENT_MILLIS = -120_000;
/**
* For {@link BroadcastQueueModernImpl}: Maximum number of complete
@@ -313,6 +322,8 @@
DEFAULT_DELAY_NORMAL_MILLIS);
DELAY_CACHED_MILLIS = getDeviceConfigLong(KEY_DELAY_CACHED_MILLIS,
DEFAULT_DELAY_CACHED_MILLIS);
+ DELAY_URGENT_MILLIS = getDeviceConfigLong(KEY_DELAY_URGENT_MILLIS,
+ DEFAULT_DELAY_URGENT_MILLIS);
MAX_HISTORY_COMPLETE_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_COMPLETE_SIZE,
DEFAULT_MAX_HISTORY_COMPLETE_SIZE);
MAX_HISTORY_SUMMARY_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_SUMMARY_SIZE,
@@ -354,6 +365,8 @@
TimeUtils.formatDuration(DELAY_NORMAL_MILLIS)).println();
pw.print(KEY_DELAY_CACHED_MILLIS,
TimeUtils.formatDuration(DELAY_CACHED_MILLIS)).println();
+ pw.print(KEY_DELAY_URGENT_MILLIS,
+ TimeUtils.formatDuration(DELAY_URGENT_MILLIS)).println();
pw.print(KEY_MAX_HISTORY_COMPLETE_SIZE, MAX_HISTORY_COMPLETE_SIZE).println();
pw.print(KEY_MAX_HISTORY_SUMMARY_SIZE, MAX_HISTORY_SUMMARY_SIZE).println();
pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 868c3ae..f7d24e9 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -85,9 +85,16 @@
@Nullable ProcessRecord app;
/**
- * Track name to use for {@link Trace} events.
+ * Track name to use for {@link Trace} events, defined as part of upgrading
+ * into a running slot.
*/
- @Nullable String traceTrackName;
+ @Nullable String runningTraceTrackName;
+
+ /**
+ * Flag indicating if this process should be OOM adjusted, defined as part
+ * of upgrading into a running slot.
+ */
+ boolean runningOomAdjusted;
/**
* Snapshotted value of {@link ProcessRecord#getCpuDelayTime()}, typically
@@ -141,7 +148,8 @@
private boolean mActiveViaColdStart;
/**
- * Count of {@link #mPending} broadcasts of these various flavors.
+ * Count of {@link #mPending} and {@link #mPendingUrgent} broadcasts of
+ * these various flavors.
*/
private int mCountForeground;
private int mCountOrdered;
@@ -150,6 +158,7 @@
private int mCountInteractive;
private int mCountResultTo;
private int mCountInstrumented;
+ private int mCountManifest;
private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
private @Reason int mRunnableAtReason = REASON_EMPTY;
@@ -206,7 +215,7 @@
// with implicit responsiveness expectations.
final ArrayDeque<SomeArgs> queue = record.isUrgent() ? mPendingUrgent : mPending;
queue.addLast(newBroadcastArgs);
- onBroadcastEnqueued(record);
+ onBroadcastEnqueued(record, recordIndex);
}
/**
@@ -224,7 +233,8 @@
while (it.hasNext()) {
final SomeArgs args = it.next();
final BroadcastRecord testRecord = (BroadcastRecord) args.arg1;
- final Object testReceiver = testRecord.receivers.get(args.argi1);
+ final int testRecordIndex = args.argi1;
+ final Object testReceiver = testRecord.receivers.get(testRecordIndex);
if ((record.callingUid == testRecord.callingUid)
&& (record.userId == testRecord.userId)
&& record.intent.filterEquals(testRecord.intent)
@@ -233,8 +243,8 @@
args.arg1 = record;
args.argi1 = recordIndex;
args.argi2 = blockedUntilTerminalCount;
- onBroadcastDequeued(testRecord);
- onBroadcastEnqueued(record);
+ onBroadcastDequeued(testRecord, testRecordIndex);
+ onBroadcastEnqueued(record, recordIndex);
return true;
}
}
@@ -284,13 +294,13 @@
while (it.hasNext()) {
final SomeArgs args = it.next();
final BroadcastRecord record = (BroadcastRecord) args.arg1;
- final int index = args.argi1;
- if (predicate.test(record, index)) {
- consumer.accept(record, index);
+ final int recordIndex = args.argi1;
+ if (predicate.test(record, recordIndex)) {
+ consumer.accept(record, recordIndex);
if (andRemove) {
args.recycle();
it.remove();
- onBroadcastDequeued(record);
+ onBroadcastDequeued(record, recordIndex);
}
didSomething = true;
}
@@ -339,7 +349,7 @@
* Return if we know of an actively running "warm" process for this queue.
*/
public boolean isProcessWarm() {
- return (app != null) && (app.getThread() != null) && !app.isKilled();
+ return (app != null) && (app.getOnewayThread() != null) && !app.isKilled();
}
public int getPreferredSchedulingGroupLocked() {
@@ -385,7 +395,7 @@
mActiveCountSinceIdle++;
mActiveViaColdStart = false;
next.recycle();
- onBroadcastDequeued(mActive);
+ onBroadcastDequeued(mActive, mActiveIndex);
}
/**
@@ -403,7 +413,7 @@
/**
* Update summary statistics when the given record has been enqueued.
*/
- private void onBroadcastEnqueued(@NonNull BroadcastRecord record) {
+ private void onBroadcastEnqueued(@NonNull BroadcastRecord record, int recordIndex) {
if (record.isForeground()) {
mCountForeground++;
}
@@ -425,13 +435,16 @@
if (record.callerInstrumented) {
mCountInstrumented++;
}
+ if (record.receivers.get(recordIndex) instanceof ResolveInfo) {
+ mCountManifest++;
+ }
invalidateRunnableAt();
}
/**
* Update summary statistics when the given record has been dequeued.
*/
- private void onBroadcastDequeued(@NonNull BroadcastRecord record) {
+ private void onBroadcastDequeued(@NonNull BroadcastRecord record, int recordIndex) {
if (record.isForeground()) {
mCountForeground--;
}
@@ -453,34 +466,37 @@
if (record.callerInstrumented) {
mCountInstrumented--;
}
+ if (record.receivers.get(recordIndex) instanceof ResolveInfo) {
+ mCountManifest--;
+ }
invalidateRunnableAt();
}
public void traceProcessStartingBegin() {
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- traceTrackName, toShortString() + " starting", hashCode());
+ runningTraceTrackName, toShortString() + " starting", hashCode());
}
public void traceProcessRunningBegin() {
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- traceTrackName, toShortString() + " running", hashCode());
+ runningTraceTrackName, toShortString() + " running", hashCode());
}
public void traceProcessEnd() {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- traceTrackName, hashCode());
+ runningTraceTrackName, hashCode());
}
public void traceActiveBegin() {
final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- traceTrackName, mActive.toShortString() + " scheduled", cookie);
+ runningTraceTrackName, mActive.toShortString() + " scheduled", cookie);
}
public void traceActiveEnd() {
final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- traceTrackName, cookie);
+ runningTraceTrackName, cookie);
}
/**
@@ -540,6 +556,14 @@
}
/**
+ * Quickly determine if this queue has broadcasts waiting to be delivered to
+ * manifest receivers, which indicates we should request an OOM adjust.
+ */
+ public boolean isPendingManifest() {
+ return mCountManifest > 0;
+ }
+
+ /**
* Quickly determine if this queue has broadcasts that are still waiting to
* be delivered at some point in the future.
*/
@@ -668,17 +692,18 @@
return;
}
- // If we have too many broadcasts pending, bypass any delays that
- // might have been applied above to aid draining
- if (mPending.size() + mPendingUrgent.size() >= constants.MAX_PENDING_BROADCASTS) {
- mRunnableAt = runnableAt;
- mRunnableAtReason = REASON_MAX_PENDING;
- return;
- }
-
if (mCountForeground > 0) {
- mRunnableAt = runnableAt;
+ mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_CONTAINS_FOREGROUND;
+ } else if (mCountInteractive > 0) {
+ mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
+ mRunnableAtReason = REASON_CONTAINS_INTERACTIVE;
+ } else if (mCountInstrumented > 0) {
+ mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
+ mRunnableAtReason = REASON_CONTAINS_INSTRUMENTED;
+ } else if (mProcessInstrumented) {
+ mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
+ mRunnableAtReason = REASON_INSTRUMENTED;
} else if (mCountOrdered > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_ORDERED;
@@ -688,18 +713,9 @@
} else if (mCountPrioritized > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_PRIORITIZED;
- } else if (mCountInteractive > 0) {
- mRunnableAt = runnableAt;
- mRunnableAtReason = REASON_CONTAINS_INTERACTIVE;
} else if (mCountResultTo > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
- } else if (mCountInstrumented > 0) {
- mRunnableAt = runnableAt;
- mRunnableAtReason = REASON_CONTAINS_INSTRUMENTED;
- } else if (mProcessInstrumented) {
- mRunnableAt = runnableAt;
- mRunnableAtReason = REASON_INSTRUMENTED;
} else if (mProcessCached) {
mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
mRunnableAtReason = REASON_CACHED;
@@ -707,6 +723,13 @@
mRunnableAt = runnableAt + constants.DELAY_NORMAL_MILLIS;
mRunnableAtReason = REASON_NORMAL;
}
+
+ // If we have too many broadcasts pending, bypass any delays that
+ // might have been applied above to aid draining
+ if (mPending.size() + mPendingUrgent.size() >= constants.MAX_PENDING_BROADCASTS) {
+ mRunnableAt = runnableAt;
+ mRunnableAtReason = REASON_MAX_PENDING;
+ }
} else {
mRunnableAt = Long.MAX_VALUE;
mRunnableAtReason = REASON_EMPTY;
@@ -808,7 +831,7 @@
@NeverCompile
public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
- if ((mActive == null) && mPending.isEmpty()) return;
+ if ((mActive == null) && isEmpty()) return;
pw.print(toShortString());
if (isRunnable()) {
@@ -824,6 +847,10 @@
if (mActive != null) {
dumpRecord(now, pw, mActive, mActiveIndex, mActiveBlockedUntilTerminalCount);
}
+ for (SomeArgs args : mPendingUrgent) {
+ final BroadcastRecord r = (BroadcastRecord) args.arg1;
+ dumpRecord(now, pw, r, args.argi1, args.argi2);
+ }
for (SomeArgs args : mPending) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
dumpRecord(now, pw, r, args.argi1, args.argi2);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.md b/services/core/java/com/android/server/am/BroadcastQueue.md
new file mode 100644
index 0000000..8131793
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastQueue.md
@@ -0,0 +1,98 @@
+# Broadcast Queue Design
+
+Broadcast intents are one of the major building blocks of the Android platform,
+generally intended for asynchronous notification of events. There are three
+flavors of intents that can be broadcast:
+
+* **Normal** broadcast intents are dispatched to relevant receivers.
+* **Ordered** broadcast intents are dispatched in a specific order to
+receivers, where each receiver has the opportunity to influence the final
+"result" of a broadcast, including aborting delivery to any remaining receivers.
+* **Sticky** broadcast intents are dispatched to relevant receivers, and are
+then retained internally for immediate dispatch to any future receivers. (This
+capability has been deprecated and its use is discouraged due to its system
+health impact.)
+
+And there are there two ways to receive these intents:
+
+* Registered receivers (via `Context.registerReceiver()` methods) are
+dynamically requested by a running app to receive intents. These requests are
+only maintained while the process is running, and are discarded at process
+death.
+* Manifest receivers (via the `<receiver>` tag in `AndroidManifest.xml`) are
+statically requested by an app to receive intents. These requests are delivered
+regardless of process running state, and have the ability to cold-start a
+process that isn't currently running.
+
+## Per-process queues
+
+The design of `BroadcastQueueModernImpl` is centered around maintaining a
+separate `BroadcastProcessQueue` instance for each potential process on the
+device. At this level, a process refers to the `android:process` attributes
+defined in `AndroidManifest.xml` files, which means it can be defined and
+populated regardless of the process state. (For example, a given
+`android:process` can have multiple `ProcessRecord`/PIDs defined as it's
+launched, killed, and relaunched over long periods of time.)
+
+Each per-process queue has the concept of a _runnable at_ timestamp when it's
+next eligible for execution, and that value can be influenced by a wide range
+of policies, such as:
+
+* Which broadcasts are pending dispatch to a given process. For example, an
+"urgent" broadcast typically results in an earlier _runnable at_ time, or a
+"delayed" broadcast typically results in a later _runnable at_ time.
+* Current state of the process or UID. For example, a "cached" process
+typically results in a later _runnable at_ time, or an "instrumented" process
+typically results in an earlier _runnable at_ time.
+* Blocked waiting for an earlier receiver to complete. For example, an
+"ordered" or "prioritized" broadcast typically results in a _not currently
+runnable_ value.
+
+Each per-process queue represents a single remote `ApplicationThread`, and we
+only dispatch a single broadcast at a time to each process to ensure developers
+see consistent ordering of broadcast events. The flexible _runnable at_
+policies above mean that no inter-process ordering guarantees are provided,
+except for those explicitly provided by "ordered" or "prioritized" broadcasts.
+
+## Parallel dispatch
+
+Given a collection of per-process queues with valid _runnable at_ timestamps,
+BroadcastQueueModernImpl is then willing to promote those _runnable_ queues
+into a _running_ state. We choose the next per-process queue to promote based
+on the sorted ordering of the _runnable at_ timestamps, selecting the
+longest-waiting process first, which aims to reduce overall broadcast dispatch
+latency.
+
+To preserve system health, at most
+`BroadcastConstants.MAX_RUNNING_PROCESS_QUEUES` processes are allowed to be in
+the _running_ state at any given time, and at most one process is allowed to be
+_cold started_ at any given time. (For background, _cold starting_ a process
+by forking and specializing the zygote is a relatively heavy operation, so
+limiting ourselves to a single pending _cold start_ reduces system-wide
+resource contention.)
+
+After each broadcast is dispatched to a given process, we consider dispatching
+any additional pending broadcasts to that process, aimed at batching dispatch
+to better amortize the cost of OOM adjustments.
+
+## Starvation considerations
+
+Careful attention is given to several types of potential resource starvation,
+along with the mechanisms of mitigation:
+
+* A per-process queue that has a delayed _runnable at_ policy applied can risk
+growing very large. This is mitigated by
+`BroadcastConstants.MAX_PENDING_BROADCASTS` bypassing any delays when the queue
+grows too large.
+* A per-process queue that has a large number of pending broadcasts can risk
+monopolizing one of the limited _runnable_ slots. This is mitigated by
+`BroadcastConstants.MAX_RUNNING_ACTIVE_BROADCASTS` being used to temporarily
+"retire" a running process to give other processes a chance to run.
+* An "urgent" broadcast dispatched to a process with a large backlog of
+"non-urgent" broadcasts can risk large dispatch latencies. This is mitigated
+by maintaining a separate `mPendingUrgent` queue of urgent events, which we
+prefer to dispatch before the normal `mPending` queue.
+* A process with a scheduled broadcast desires to execute, but heavy CPU
+contention can risk the process not receiving enough resources before an ANR
+timeout is triggered. This is mitigated by extending the "soft" ANR timeout by
+up to double the original timeout length.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index db3ef3d..9e9eb71 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -58,6 +58,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.os.BundleMerger;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
@@ -210,6 +211,12 @@
private final BroadcastConstants mFgConstants;
private final BroadcastConstants mBgConstants;
+ /**
+ * Timestamp when last {@link #testAllProcessQueues} failure was observed;
+ * used for throttling log messages.
+ */
+ private @UptimeMillisLong long mLastTestFailureTime;
+
private static final int MSG_UPDATE_RUNNING_LIST = 1;
private static final int MSG_DELIVERY_TIMEOUT_SOFT = 2;
private static final int MSG_DELIVERY_TIMEOUT_HARD = 3;
@@ -358,6 +365,13 @@
BroadcastProcessQueue nextQueue = queue.runnableAtNext;
final long runnableAt = queue.getRunnableAt();
+ // When broadcasts are skipped or failed during list traversal, we
+ // might encounter a queue that is no longer runnable; skip it
+ if (!queue.isRunnable()) {
+ queue = nextQueue;
+ continue;
+ }
+
// If queues beyond this point aren't ready to run yet, schedule
// another pass when they'll be runnable
if (runnableAt > now && !waitingFor) {
@@ -395,7 +409,8 @@
mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
// Emit all trace events for this process into a consistent track
- queue.traceTrackName = TAG + ".mRunning[" + queueIndex + "]";
+ queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]";
+ queue.runningOomAdjusted = queue.isPendingManifest();
// If we're already warm, schedule next pending broadcast now;
// otherwise we'll wait for the cold start to circle back around
@@ -409,9 +424,8 @@
scheduleReceiverColdLocked(queue);
}
- // We've moved at least one process into running state above, so we
- // need to kick off an OOM adjustment pass
- updateOomAdj = true;
+ // Only kick off an OOM adjustment pass if needed
+ updateOomAdj |= queue.runningOomAdjusted;
// Move to considering next runnable queue
queue = nextQueue;
@@ -521,6 +535,8 @@
@Override
public void enqueueBroadcastLocked(@NonNull BroadcastRecord r) {
+ if (DEBUG_BROADCAST) logv("Enqueuing " + r + " for " + r.receivers.size() + " receivers");
+
r.applySingletonPolicy(mService);
final IntentFilter removeMatchingFilter = (r.options != null)
@@ -535,16 +551,7 @@
}, mBroadcastConsumerSkipAndCanceled, true);
}
- final int policy = (r.options != null)
- ? r.options.getDeliveryGroupPolicy() : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
- if (policy == BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) {
- forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
- // We only allow caller to remove broadcasts they enqueued
- return (r.callingUid == testRecord.callingUid)
- && (r.userId == testRecord.userId)
- && r.matchesDeliveryGroup(testRecord);
- }, mBroadcastConsumerSkipAndCanceled, true);
- }
+ applyDeliveryGroupPolicy(r);
if (r.isReplacePending()) {
// Leave the skipped broadcasts intact in queue, so that we can
@@ -601,6 +608,41 @@
}
}
+ private void applyDeliveryGroupPolicy(@NonNull BroadcastRecord r) {
+ final int policy = (r.options != null)
+ ? r.options.getDeliveryGroupPolicy() : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
+ final BroadcastConsumer broadcastConsumer;
+ switch (policy) {
+ case BroadcastOptions.DELIVERY_GROUP_POLICY_ALL:
+ // Older broadcasts need to be left as is in this case, so nothing more to do.
+ return;
+ case BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT:
+ broadcastConsumer = mBroadcastConsumerSkipAndCanceled;
+ break;
+ case BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED:
+ final BundleMerger extrasMerger = r.options.getDeliveryGroupExtrasMerger();
+ if (extrasMerger == null) {
+ // Extras merger is required to be able to merge the extras. So, if it's not
+ // supplied, then ignore the delivery group policy.
+ return;
+ }
+ broadcastConsumer = (record, recordIndex) -> {
+ r.intent.mergeExtras(record.intent, extrasMerger);
+ mBroadcastConsumerSkipAndCanceled.accept(record, recordIndex);
+ };
+ break;
+ default:
+ logw("Unknown delivery group policy: " + policy);
+ return;
+ }
+ forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
+ // We only allow caller to remove broadcasts they enqueued
+ return (r.callingUid == testRecord.callingUid)
+ && (r.userId == testRecord.userId)
+ && r.matchesDeliveryGroup(testRecord);
+ }, broadcastConsumer, true);
+ }
+
/**
* Schedule the currently active broadcast on the given queue when we know
* the process is cold. This kicks off a cold start and will eventually call
@@ -728,7 +770,7 @@
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
- final IApplicationThread thread = app.getThread();
+ final IApplicationThread thread = app.getOnewayThread();
if (thread != null) {
try {
if (receiver instanceof BroadcastFilter) {
@@ -769,7 +811,7 @@
private void scheduleResultTo(@NonNull BroadcastRecord r) {
if ((r.resultToApp == null) || (r.resultTo == null)) return;
final ProcessRecord app = r.resultToApp;
- final IApplicationThread thread = app.getThread();
+ final IApplicationThread thread = app.getOnewayThread();
if (thread != null) {
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
app, OOM_ADJ_REASON_FINISH_RECEIVER);
@@ -859,10 +901,11 @@
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
}
- // Even if we have more broadcasts, if we've made reasonable progress
- // and someone else is waiting, retire ourselves to avoid starvation
- final boolean shouldRetire = (mRunnableHead != null)
- && (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
+ // If we've made reasonable progress, periodically retire ourselves to
+ // avoid starvation of other processes and stack overflow when a
+ // broadcast is immediately finished without waiting
+ final boolean shouldRetire =
+ (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
// We're on a roll; move onto the next broadcast for this process
@@ -1034,7 +1077,11 @@
BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
while (leaf != null) {
if (!test.test(leaf)) {
- logv("Test " + label + " failed due to " + leaf.toShortString(), pw);
+ final long now = SystemClock.uptimeMillis();
+ if (now > mLastTestFailureTime + DateUtils.SECOND_IN_MILLIS) {
+ mLastTestFailureTime = now;
+ logv("Test " + label + " failed due to " + leaf.toShortString(), pw);
+ }
return false;
}
leaf = leaf.processNameNext;
@@ -1232,8 +1279,6 @@
if (queue.app != null) {
queue.app.mReceivers.incrementCurReceivers();
- queue.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
-
// Don't bump its LRU position if it's in the background restricted.
if (mService.mInternal.getRestrictionLevel(
queue.uid) < ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
@@ -1243,7 +1288,10 @@
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(queue.app,
OOM_ADJ_REASON_START_RECEIVER);
- mService.enqueueOomAdjTargetLocked(queue.app);
+ if (queue.runningOomAdjusted) {
+ queue.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
+ mService.enqueueOomAdjTargetLocked(queue.app);
+ }
}
}
@@ -1253,10 +1301,11 @@
*/
private void notifyStoppedRunning(@NonNull BroadcastProcessQueue queue) {
if (queue.app != null) {
- // Update during our next pass; no need for an immediate update
- mService.enqueueOomAdjTargetLocked(queue.app);
-
queue.app.mReceivers.decrementCurReceivers();
+
+ if (queue.runningOomAdjusted) {
+ mService.enqueueOomAdjTargetLocked(queue.app);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index d7dc8b8..2a3c897 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -78,7 +78,7 @@
final int callingPid; // the pid of who sent this
final int callingUid; // the uid of who sent this
final boolean callerInstantApp; // caller is an Instant App?
- final boolean callerInstrumented; // caller is being instrumented
+ final boolean callerInstrumented; // caller is being instrumented?
final boolean ordered; // serialize the send to receivers?
final boolean sticky; // originated from existing sticky data?
final boolean alarm; // originated from an alarm triggering?
@@ -366,8 +366,7 @@
callingPid = _callingPid;
callingUid = _callingUid;
callerInstantApp = _callerInstantApp;
- callerInstrumented = (_callerApp != null)
- ? (_callerApp.getActiveInstrumentation() != null) : false;
+ callerInstrumented = isCallerInstrumented(_callerApp, _callingUid);
resolvedType = _resolvedType;
requiredPermissions = _requiredPermissions;
excludedPermissions = _excludedPermissions;
@@ -675,6 +674,17 @@
return (newIntent != null) ? newIntent : intent;
}
+ static boolean isCallerInstrumented(@Nullable ProcessRecord callerApp, int callingUid) {
+ switch (UserHandle.getAppId(callingUid)) {
+ case android.os.Process.ROOT_UID:
+ case android.os.Process.SHELL_UID:
+ // Broadcasts sent via "shell" are typically invoked by test
+ // suites, so we treat them as if the caller was instrumented
+ return true;
+ }
+ return (callerApp != null) ? (callerApp.getActiveInstrumentation() != null) : false;
+ }
+
/**
* Return if given receivers list has more than one traunch of priorities.
*/
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index d080036..dec8b62 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -101,7 +101,7 @@
30073 uc_finish_user_stopping (userId|1|5)
30074 uc_finish_user_stopped (userId|1|5)
30075 uc_switch_user (userId|1|5)
-30076 uc_start_user_internal (userId|1|5)
+30076 uc_start_user_internal (userId|1|5),(foreground|1),(displayId|1|5)
30077 uc_unlock_user (userId|1|5)
30078 uc_finish_user_boot (userId|1|5)
30079 uc_dispatch_user_switch (oldUserId|1|5),(newUserId|1|5)
@@ -109,13 +109,14 @@
30081 uc_send_user_broadcast (userId|1|5),(IntentAction|3)
# Tags below are used by SystemServiceManager - although it's technically part of am, these are
# also user switch events and useful to be analyzed together with events above.
-30082 ssm_user_starting (userId|1|5)
+30082 ssm_user_starting (userId|1|5),(visible|1)
30083 ssm_user_switching (oldUserId|1|5),(newUserId|1|5)
30084 ssm_user_unlocking (userId|1|5)
30085 ssm_user_unlocked (userId|1|5)
-30086 ssm_user_stopping (userId|1|5)
+30086 ssm_user_stopping (userId|1|5),(visibilityChanged|1)
30087 ssm_user_stopped (userId|1|5)
30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5)
+30089 ssm_user_visible (userId|1|5)
# Foreground service start/stop events.
30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 3b04dbb..0a8c640 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -54,6 +54,7 @@
import com.android.internal.app.procstats.ProcessState;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.os.Zygote;
+import com.android.server.FgThread;
import com.android.server.wm.WindowProcessController;
import com.android.server.wm.WindowProcessListener;
@@ -143,6 +144,13 @@
private IApplicationThread mThread;
/**
+ * Instance of {@link #mThread} that will always meet the {@code oneway}
+ * contract, possibly by using {@link SameProcessApplicationThread}.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private IApplicationThread mOnewayThread;
+
+ /**
* Always keep this application running?
*/
private volatile boolean mPersistent;
@@ -603,16 +611,27 @@
return mThread;
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ IApplicationThread getOnewayThread() {
+ return mOnewayThread;
+ }
+
@GuardedBy({"mService", "mProcLock"})
public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
mProfile.onProcessActive(thread, tracker);
mThread = thread;
+ if (mPid == Process.myPid()) {
+ mOnewayThread = new SameProcessApplicationThread(thread, FgThread.getHandler());
+ } else {
+ mOnewayThread = thread;
+ }
mWindowProcessController.setThread(thread);
}
@GuardedBy({"mService", "mProcLock"})
public void makeInactive(ProcessStatsService tracker) {
mThread = null;
+ mOnewayThread = null;
mWindowProcessController.setThread(null);
mProfile.onProcessInactive(tracker);
}
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
new file mode 100644
index 0000000..a3c0111
--- /dev/null
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 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 android.annotation.NonNull;
+import android.app.IApplicationThread;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+
+import java.util.Objects;
+
+/**
+ * Wrapper around an {@link IApplicationThread} that delegates selected calls
+ * through a {@link Handler} so they meet the {@code oneway} contract of
+ * returning immediately after dispatch.
+ */
+public class SameProcessApplicationThread extends IApplicationThread.Default {
+ private final IApplicationThread mWrapped;
+ private final Handler mHandler;
+
+ public SameProcessApplicationThread(@NonNull IApplicationThread wrapped,
+ @NonNull Handler handler) {
+ mWrapped = Objects.requireNonNull(wrapped);
+ mHandler = Objects.requireNonNull(handler);
+ }
+
+ @Override
+ public void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
+ int resultCode, String data, Bundle extras, boolean sync, int sendingUser,
+ int processState) {
+ mHandler.post(() -> {
+ try {
+ mWrapped.scheduleReceiver(intent, info, compatInfo, resultCode, data, extras, sync,
+ sendingUser, processState);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ @Override
+ public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode,
+ String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser,
+ int processState) {
+ mHandler.post(() -> {
+ try {
+ mWrapped.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras,
+ ordered, sticky, sendingUser, processState);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3fa41c0..dcc7a8e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -100,6 +100,7 @@
import android.util.IntArray;
import android.util.Pair;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -174,6 +175,9 @@
static final int START_USER_SWITCH_FG_MSG = 120;
static final int COMPLETE_USER_SWITCH_MSG = 130;
static final int USER_COMPLETED_EVENT_MSG = 140;
+ static final int USER_VISIBLE_MSG = 150;
+
+ private static final int NO_ARG2 = 0;
// Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if
// the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not
@@ -421,6 +425,17 @@
/** @see #getLastUserUnlockingUptime */
private volatile long mLastUserUnlockingUptime = 0;
+ /**
+ * List of visible users (as defined by {@link UserManager#isUserVisible()}).
+ *
+ * <p>It's only used to call {@link SystemServiceManager} when the visibility is changed upon
+ * the user starting or stopping.
+ *
+ * <p>Note: only the key is used, not the value.
+ */
+ @GuardedBy("mLock")
+ private final SparseBooleanArray mVisibleUsers = new SparseBooleanArray();
+
UserController(ActivityManagerService service) {
this(new Injector(service));
}
@@ -1050,11 +1065,27 @@
// instead.
userManagerInternal.unassignUserFromDisplay(userId);
+ final boolean visibilityChanged;
+ boolean visibleBefore;
+ synchronized (mLock) {
+ visibleBefore = mVisibleUsers.get(userId);
+ if (visibleBefore) {
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "Removing %d from mVisibleUsers", userId);
+ }
+ mVisibleUsers.delete(userId);
+ visibilityChanged = true;
+ } else {
+ visibilityChanged = false;
+ }
+ }
+
updateStartedUserArrayLU();
final boolean allowDelayedLockingCopied = allowDelayedLocking;
Runnable finishUserStoppingAsync = () ->
- mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied));
+ mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied,
+ visibilityChanged));
if (mInjector.getUserManager().isPreCreated(userId)) {
finishUserStoppingAsync.run();
@@ -1092,7 +1123,7 @@
}
private void finishUserStopping(final int userId, final UserState uss,
- final boolean allowDelayedLocking) {
+ final boolean allowDelayedLocking, final boolean visibilityChanged) {
EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPING, userId);
synchronized (mLock) {
if (uss.state != UserState.STATE_STOPPING) {
@@ -1109,7 +1140,7 @@
mInjector.batteryStatsServiceNoteEvent(
BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
Integer.toString(userId), userId);
- mInjector.getSystemServiceManager().onUserStopping(userId);
+ mInjector.getSystemServiceManager().onUserStopping(userId, visibilityChanged);
Runnable finishUserStoppedAsync = () ->
mHandler.post(() -> finishUserStopped(uss, allowDelayedLocking));
@@ -1513,16 +1544,17 @@
private boolean startUserInternal(@UserIdInt int userId, int displayId, boolean foreground,
@Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
if (DEBUG_MU) {
- Slogf.i(TAG, "Starting user %d on display %d %s", userId, displayId,
+ Slogf.i(TAG, "Starting user %d on display %d%s", userId, displayId,
foreground ? " in foreground" : "");
}
- if (displayId != Display.DEFAULT_DISPLAY) {
+ boolean onSecondaryDisplay = displayId != Display.DEFAULT_DISPLAY;
+ if (onSecondaryDisplay) {
Preconditions.checkArgument(!foreground, "Cannot start user %d in foreground AND "
+ "on secondary display (%d)", userId, displayId);
}
- // TODO(b/239982558): log display id (or use a new event)
- EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId);
+ EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId, foreground ? 1 : 0,
+ displayId);
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
@@ -1571,8 +1603,9 @@
return false;
}
- if (foreground && userInfo.preCreated) {
- Slogf.w(TAG, "Cannot start pre-created user #" + userId + " as foreground");
+ if ((foreground || onSecondaryDisplay) && userInfo.preCreated) {
+ Slogf.w(TAG, "Cannot start pre-created user #" + userId + " in foreground or on "
+ + "secondary display");
return false;
}
@@ -1656,6 +1689,28 @@
}
t.traceEnd();
+ // Need to call UM when user is on background, as there are some cases where the user
+ // cannot be started in background on a secondary display (for example, if user is a
+ // profile).
+ // TODO(b/253103846): it's also explicitly checking if the user is the USER_SYSTEM, as
+ // the UM call would return true during boot (when CarService / BootUserInitializer
+ // calls AM.startUserInBackground() because the system user is still the current user.
+ // TODO(b/244644281): another fragility of this check is that it must wait to call
+ // UMI.isUserVisible() until the user state is check, as that method checks if the
+ // profile of the current user is started. We should fix that dependency so the logic
+ // belongs to just one place (like UserDisplayAssigner)
+ boolean visible = foreground
+ || userId != UserHandle.USER_SYSTEM
+ && mInjector.getUserManagerInternal().isUserVisible(userId);
+ if (visible) {
+ synchronized (mLock) {
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "Adding %d to mVisibleUsers", userId);
+ }
+ mVisibleUsers.put(userId, true);
+ }
+ }
+
// Make sure user is in the started state. If it is currently
// stopping, we need to knock that off.
if (uss.state == UserState.STATE_STOPPING) {
@@ -1692,8 +1747,15 @@
// Booting up a new user, need to tell system services about it.
// Note that this is on the same handler as scheduling of broadcasts,
// which is important because it needs to go first.
- mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, 0));
+ mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId,
+ visible ? 1 : 0));
t.traceEnd();
+ } else if (visible) {
+ // User was already running and became visible (for example, when switching to a
+ // user that was started in the background before), so it's necessary to explicitly
+ // notify the services (while when the user starts from BOOTING, USER_START_MSG
+ // takes care of that.
+ mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBLE_MSG, userId, NO_ARG2));
}
t.traceBegin("sendMessages");
@@ -2110,6 +2172,11 @@
mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0));
stopGuestOrEphemeralUserIfBackground(oldUserId);
stopUserOnSwitchIfEnforced(oldUserId);
+ if (oldUserId == UserHandle.USER_SYSTEM) {
+ // System user is never stopped, but its visibility is changed (as it is brought to the
+ // background)
+ updateSystemUserVisibility(/* visible= */ false);
+ }
t.traceEnd(); // end continueUserSwitch
}
@@ -2413,9 +2480,7 @@
void setAllowUserUnlocking(boolean allowed) {
mAllowUserUnlocking = allowed;
if (DEBUG_MU) {
- // TODO(b/245335748): use Slogf.d instead
- // Slogf.d(TAG, new Exception(), "setAllowUserUnlocking(%b)", allowed);
- android.util.Slog.d(TAG, "setAllowUserUnlocking():" + allowed, new Exception());
+ Slogf.d(TAG, new Exception(), "setAllowUserUnlocking(%b)", allowed);
}
}
@@ -2457,10 +2522,34 @@
}
void onSystemReady() {
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "onSystemReady()");
+
+ }
updateCurrentProfileIds();
mInjector.reportCurWakefulnessUsageEvent();
}
+ // TODO(b/242195409): remove this method if initial system user boot logic is refactored?
+ void onSystemUserStarting() {
+ updateSystemUserVisibility(/* visible= */ !UserManager.isHeadlessSystemUserMode());
+ }
+
+ private void updateSystemUserVisibility(boolean visible) {
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "updateSystemUserVisibility(): visible=%b", visible);
+ }
+ int userId = UserHandle.USER_SYSTEM;
+ synchronized (mLock) {
+ if (visible) {
+ mVisibleUsers.put(userId, true);
+ } else {
+ mVisibleUsers.delete(userId);
+ }
+ }
+ mInjector.onUserStarting(userId, visible);
+ }
+
/**
* Refreshes the list of users related to the current user when either a
* user switch happens or when a new related user is started in the
@@ -2846,6 +2935,9 @@
proto.end(uToken);
}
}
+ for (int i = 0; i < mVisibleUsers.size(); i++) {
+ proto.write(UserControllerProto.VISIBLE_USERS_ARRAY, mVisibleUsers.keyAt(i));
+ }
proto.end(token);
}
}
@@ -2899,7 +2991,8 @@
if (mSwitchingToSystemUserMessage != null) {
pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage);
}
- pw.println(" mLastUserUnlockingUptime:" + mLastUserUnlockingUptime);
+ pw.println(" mLastUserUnlockingUptime: " + mLastUserUnlockingUptime);
+ pw.println(" mVisibleUsers: " + mVisibleUsers);
}
}
@@ -2936,8 +3029,7 @@
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
- mInjector.getSystemServiceManager().onUserStarting(
- TimingsTraceAndSlog.newAsyncLog(), msg.arg1);
+ mInjector.onUserStarting(/* userId= */ msg.arg1, /* visible= */ msg.arg2 == 1);
scheduleOnUserCompletedEvent(msg.arg1,
UserCompletedEventType.EVENT_TYPE_USER_STARTING,
USER_COMPLETED_EVENT_DELAY_MS);
@@ -3018,6 +3110,9 @@
case COMPLETE_USER_SWITCH_MSG:
completeUserSwitch(msg.arg1);
break;
+ case USER_VISIBLE_MSG:
+ mInjector.getSystemServiceManager().onUserVisible(/* userId= */ msg.arg1);
+ break;
}
return false;
}
@@ -3327,6 +3422,14 @@
}
EventLog.writeEvent(EventLogTags.UC_SEND_USER_BROADCAST, logUserId, intent.getAction());
+ // When the modern broadcast stack is enabled, deliver all our
+ // broadcasts as unordered, since the modern stack has better
+ // support for sequencing cold-starts, and it supports delivering
+ // resultTo for non-ordered broadcasts
+ if (mService.mEnableModernQueue) {
+ ordered = false;
+ }
+
// TODO b/64165549 Verify that mLock is not held before calling AMS methods
synchronized (mService) {
return mService.broadcastIntentLocked(null, null, null, intent, resolvedType,
@@ -3531,5 +3634,10 @@
boolean isUsersOnSecondaryDisplaysEnabled() {
return UserManager.isUsersOnSecondaryDisplaysEnabled();
}
+
+ void onUserStarting(int userId, boolean visible) {
+ getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId,
+ visible);
+ }
}
}
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index 1162498..1e68837 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -21,12 +21,12 @@
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.app.GameManagerService.GamePackageConfiguration;
import com.android.server.app.GameManagerService.GamePackageConfiguration.GameModeConfiguration;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index bc650ad..a58583c 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -131,8 +131,6 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -152,6 +150,8 @@
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index dd0c4b86..10243e2 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -53,13 +53,13 @@
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import java.io.File;
import java.io.FileInputStream;
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 2c68aaf..bd9d057 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -52,8 +52,6 @@
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.TimeUtils;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -62,6 +60,8 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.FgThread;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cbfd17f0..f3a9a69 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4024,7 +4024,7 @@
}
}
- private void setLeAudioVolumeOnModeUpdate(int mode, int streamType, int device) {
+ private void setLeAudioVolumeOnModeUpdate(int mode, int device) {
switch (mode) {
case AudioSystem.MODE_IN_COMMUNICATION:
case AudioSystem.MODE_IN_CALL:
@@ -4038,10 +4038,16 @@
return;
}
- // Currently, DEVICE_OUT_BLE_HEADSET is the only output type for LE_AUDIO profile.
- // (See AudioDeviceBroker#createBtDeviceInfo())
- int index = mStreamStates[streamType].getIndex(AudioSystem.DEVICE_OUT_BLE_HEADSET);
- int maxIndex = mStreamStates[streamType].getMaxIndex();
+ // Forcefully set LE audio volume as a workaround, since in some cases
+ // (like the outgoing call) the value of 'device' is not DEVICE_OUT_BLE_*
+ // even when BLE is connected.
+ if (!AudioSystem.isLeAudioDeviceType(device)) {
+ device = AudioSystem.DEVICE_OUT_BLE_HEADSET;
+ }
+
+ final int streamType = getBluetoothContextualVolumeStream(mode);
+ final int index = mStreamStates[streamType].getIndex(device);
+ final int maxIndex = mStreamStates[streamType].getMaxIndex();
if (DEBUG_VOL) {
Log.d(TAG, "setLeAudioVolumeOnModeUpdate postSetLeAudioVolumeIndex index="
@@ -5427,9 +5433,7 @@
// change of mode may require volume to be re-applied on some devices
updateAbsVolumeMultiModeDevices(previousMode, mode);
- // Forcefully set LE audio volume as a workaround, since the value of 'device'
- // is not DEVICE_OUT_BLE_* even when BLE is connected.
- setLeAudioVolumeOnModeUpdate(mode, streamType, device);
+ setLeAudioVolumeOnModeUpdate(mode, device);
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index 1a5f31c..da43618 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -52,16 +52,13 @@
private boolean mDestroyed = false;
private boolean mDestroyRequested = false;
private boolean mDisableRequested = false;
- private volatile NextConsumer mNextConsumer = null;
+ private NextConsumer mNextConsumer = null;
private volatile float mLastAmbientLux = -1;
private final SensorEventListener mLightSensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
- mLastAmbientLux = event.values[0];
- if (mNextConsumer != null) {
- completeNextConsumer(mLastAmbientLux);
- }
+ onNext(event.values[0]);
}
@Override
@@ -133,11 +130,29 @@
// if a final consumer is set it will call destroy/disable on the next value if requested
if (!mDestroyed && mNextConsumer == null) {
- disable();
+ disableLightSensorLoggingLocked();
mDestroyed = true;
}
}
+ private synchronized void onNext(float value) {
+ mLastAmbientLux = value;
+
+ final NextConsumer consumer = mNextConsumer;
+ mNextConsumer = null;
+ if (consumer != null) {
+ Slog.v(TAG, "Finishing next consumer");
+
+ if (mDestroyRequested) {
+ destroy();
+ } else if (mDisableRequested) {
+ disable();
+ }
+
+ consumer.consume(value);
+ }
+ }
+
/** The most recent lux reading. */
public float getMostRecentLux() {
return mLastAmbientLux;
@@ -160,7 +175,7 @@
@Nullable Handler handler) {
final NextConsumer nextConsumer = new NextConsumer(consumer, handler);
final float current = mLastAmbientLux;
- if (current > 0) {
+ if (current > -1f) {
nextConsumer.consume(current);
} else if (mDestroyed) {
nextConsumer.consume(-1f);
@@ -172,23 +187,6 @@
}
}
- private synchronized void completeNextConsumer(float value) {
- Slog.v(TAG, "Finishing next consumer");
-
- final NextConsumer consumer = mNextConsumer;
- mNextConsumer = null;
-
- if (mDestroyRequested) {
- destroy();
- } else if (mDisableRequested) {
- disable();
- }
-
- if (consumer != null) {
- consumer.consume(value);
- }
- }
-
private void enableLightSensorLoggingLocked() {
if (!mEnabled) {
mEnabled = true;
@@ -219,9 +217,13 @@
}
}
- private void onTimeout() {
+ private synchronized void onTimeout() {
Slog.e(TAG, "Max time exceeded for ALS logger - disabling: "
+ mLightSensorListener.hashCode());
+
+ // if consumers are waiting but there was no sensor change, complete them with the latest
+ // value before disabling
+ onNext(mLastAmbientLux);
disable();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java b/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java
index 49cddaa..7fb27b6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java
@@ -23,11 +23,11 @@
import android.os.Environment;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 2761ec0..7a5b584 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -600,8 +600,9 @@
}
try {
final SensorProps[] props = face.getSensorProps();
- final FaceProvider provider = new FaceProvider(getContext(), props, instance,
- mLockoutResetDispatcher, BiometricContext.getInstance(getContext()));
+ final FaceProvider provider = new FaceProvider(getContext(),
+ mBiometricStateCallback, props, instance, mLockoutResetDispatcher,
+ BiometricContext.getInstance(getContext()));
providers.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -612,14 +613,14 @@
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
- @Override // Binder call
public void registerAuthenticators(
@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
mRegistry.registerAll(() -> {
final List<ServiceProvider> providers = new ArrayList<>();
for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
providers.add(
- Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
+ Face10.newInstance(getContext(), mBiometricStateCallback,
+ hidlSensor, mLockoutResetDispatcher));
}
providers.addAll(getAidlProviders());
return providers;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUserState.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUserState.java
index a9981d0..5a82b3a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceUserState.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUserState.java
@@ -19,10 +19,10 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.face.Face;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.biometrics.sensors.BiometricUserState;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 73c272f..cfbb5dc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -16,8 +16,6 @@
package com.android.server.biometrics.sensors.face.aidl;
-import static android.Manifest.permission.TEST_BIOMETRIC;
-
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.ITestSession;
@@ -33,7 +31,6 @@
import android.util.Slog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
-import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.face.FaceUtils;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index c12994c..6488185 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -52,9 +52,11 @@
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.InvalidationRequesterClient;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.PerformanceTracker;
@@ -81,6 +83,7 @@
private boolean mTestHalEnabled;
@NonNull private final Context mContext;
+ @NonNull private final BiometricStateCallback mBiometricStateCallback;
@NonNull private final String mHalInstanceName;
@NonNull @VisibleForTesting
final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@@ -122,11 +125,14 @@
}
}
- public FaceProvider(@NonNull Context context, @NonNull SensorProps[] props,
+ public FaceProvider(@NonNull Context context,
+ @NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull BiometricContext biometricContext) {
mContext = context;
+ mBiometricStateCallback = biometricStateCallback;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
mHandler = new Handler(Looper.getMainLooper());
@@ -363,16 +369,18 @@
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext, maxTemplatesPerUser, debugConsent);
- scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
- });
+ scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+ mBiometricStateCallback, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
+ if (success) {
+ scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+ scheduleInvalidationRequest(sensorId, userId);
+ }
+ }
+ }));
});
return id;
}
@@ -396,7 +404,7 @@
token, id, callback, userId, opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric);
- scheduleForSensor(sensorId, client);
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
return id;
@@ -424,7 +432,7 @@
mBiometricContext, isStrongBiometric,
mUsageStats, mSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication, isKeyguardBypassEnabled, biometricStrength);
- scheduleForSensor(sensorId, client);
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
@@ -479,7 +487,7 @@
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client);
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
@@ -568,7 +576,8 @@
if (favorHalEnrollments) {
client.setFavorHalEnrollments();
}
- scheduleForSensor(sensorId, client, callback);
+ scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(callback,
+ mBiometricStateCallback));
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index 14af216..7a6a274f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -16,8 +16,6 @@
package com.android.server.biometrics.sensors.face.hidl;
-import static android.Manifest.permission.TEST_BIOMETRIC;
-
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.ITestSession;
@@ -30,7 +28,6 @@
import android.os.RemoteException;
import android.util.Slog;
-import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.face.FaceUtils;
@@ -53,6 +50,7 @@
@NonNull private final Set<Integer> mEnrollmentIds;
@NonNull private final Random mRandom;
+
private final IFaceServiceReceiver mReceiver = new IFaceServiceReceiver.Stub() {
@Override
public void onEnrollResult(Face face, int remaining) {
@@ -116,7 +114,8 @@
};
BiometricTestSessionImpl(@NonNull Context context, int sensorId,
- @NonNull ITestSessionCallback callback, @NonNull Face10 face10,
+ @NonNull ITestSessionCallback callback,
+ @NonNull Face10 face10,
@NonNull Face10.HalResultController halResultController) {
mContext = context;
mSensorId = sensorId;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index c0a119f..0e0ee19 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -62,8 +62,10 @@
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -110,6 +112,7 @@
private boolean mTestHalEnabled;
@NonNull private final FaceSensorPropertiesInternal mSensorProperties;
+ @NonNull private final BiometricStateCallback mBiometricStateCallback;
@NonNull private final Context mContext;
@NonNull private final BiometricScheduler mScheduler;
@NonNull private final Handler mHandler;
@@ -336,6 +339,7 @@
@VisibleForTesting
Face10(@NonNull Context context,
+ @NonNull BiometricStateCallback biometricStateCallback,
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull Handler handler,
@@ -343,6 +347,7 @@
@NonNull BiometricContext biometricContext) {
mSensorProperties = sensorProps;
mContext = context;
+ mBiometricStateCallback = biometricStateCallback;
mSensorId = sensorProps.sensorId;
mScheduler = scheduler;
mHandler = handler;
@@ -366,11 +371,12 @@
}
public static Face10 newInstance(@NonNull Context context,
+ @NonNull BiometricStateCallback biometricStateCallback,
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
final Handler handler = new Handler(Looper.getMainLooper());
- return new Face10(context, sensorProps, lockoutResetDispatcher, handler,
- new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
+ return new Face10(context, biometricStateCallback, sensorProps, lockoutResetDispatcher,
+ handler, new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
null /* gestureAvailabilityTracker */),
BiometricContext.getInstance(context));
}
@@ -615,8 +621,19 @@
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ mBiometricStateCallback.onClientStarted(clientMonitor);
+ }
+
+ @Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+
+ @Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
+ mBiometricStateCallback.onClientFinished(clientMonitor, success);
if (success) {
// Update authenticatorIds
scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
@@ -661,7 +678,7 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric, mLockoutTracker,
mUsageStats, allowBackgroundAuthentication, isKeyguardBypassEnabled);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
}
@@ -696,7 +713,7 @@
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
}
@@ -714,7 +731,7 @@
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
}
@@ -806,14 +823,15 @@
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext, enrolledList,
FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, callback);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
+ mBiometricStateCallback));
});
}
@Override
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback) {
- scheduleInternalCleanup(userId, callback);
+ scheduleInternalCleanup(userId, mBiometricStateCallback);
}
@Override
@@ -1011,7 +1029,7 @@
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
- return new BiometricTestSessionImpl(mContext, mSensorId, callback, this,
- mHalResultController);
+ return new BiometricTestSessionImpl(mContext, mSensorId, callback,
+ this, mHalResultController);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUserState.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUserState.java
index ae173f7..b1a9ef1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUserState.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUserState.java
@@ -19,10 +19,10 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.fingerprint.Fingerprint;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.biometrics.sensors.BiometricUserState;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 17ba07f..628c16a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -384,28 +384,18 @@
mBiometricContext,
mSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason);
- scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
-
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- mBiometricStateCallback.onClientStarted(clientMonitor);
- }
-
- @Override
- public void onBiometricAction(int action) {
- mBiometricStateCallback.onBiometricAction(action);
- }
-
+ scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+ mBiometricStateCallback, new ClientMonitorCallback() {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- mBiometricStateCallback.onClientFinished(clientMonitor, success);
+ ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
if (success) {
scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
scheduleInvalidationRequest(sensorId, userId);
}
}
- });
+ }));
});
return id;
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6795b6b..45b0f0a6 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -79,6 +79,7 @@
import android.net.RouteInfo;
import android.net.UidRangeParcel;
import android.net.UnderlyingNetworkInfo;
+import android.net.Uri;
import android.net.VpnManager;
import android.net.VpnProfileState;
import android.net.VpnService;
@@ -226,6 +227,16 @@
private static final int VPN_DEFAULT_SCORE = 101;
/**
+ * The reset session timer for data stall. If a session has not successfully revalidated after
+ * the delay, the session will be torn down and restarted in an attempt to recover. Delay
+ * counter is reset on successful validation only.
+ *
+ * <p>If retries have exceeded the length of this array, the last entry in the array will be
+ * used as a repeating interval.
+ */
+ private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L};
+
+ /**
* The initial token value of IKE session.
*/
private static final int STARTING_TOKEN = -1;
@@ -271,6 +282,7 @@
private final UserManager mUserManager;
private final VpnProfileStore mVpnProfileStore;
+ protected boolean mDataStallSuspected = false;
@VisibleForTesting
VpnProfileStore getVpnProfileStore() {
@@ -522,10 +534,28 @@
@NonNull LinkProperties lp,
@NonNull NetworkScore score,
@NonNull NetworkAgentConfig config,
- @Nullable NetworkProvider provider) {
+ @Nullable NetworkProvider provider,
+ @Nullable ValidationStatusCallback callback) {
return new VpnNetworkAgentWrapper(
- context, looper, logTag, nc, lp, score, config, provider);
+ context, looper, logTag, nc, lp, score, config, provider, callback);
}
+
+ /**
+ * Get the length of time to wait before resetting the ike session when a data stall is
+ * suspected.
+ */
+ public long getDataStallResetSessionSeconds(int count) {
+ if (count >= DATA_STALL_RESET_DELAYS_SEC.length) {
+ return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1];
+ } else {
+ return DATA_STALL_RESET_DELAYS_SEC[count];
+ }
+ }
+ }
+
+ @VisibleForTesting
+ interface ValidationStatusCallback {
+ void onValidationStatus(int status);
}
public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd,
@@ -1460,6 +1490,11 @@
@GuardedBy("this")
private void agentConnect() {
+ agentConnect(null /* validationCallback */);
+ }
+
+ @GuardedBy("this")
+ private void agentConnect(@Nullable ValidationStatusCallback validationCallback) {
LinkProperties lp = makeLinkProperties();
// VPN either provide a default route (IPv4 or IPv6 or both), or they are a split tunnel
@@ -1507,7 +1542,7 @@
mNetworkAgent = mDeps.newNetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
mNetworkCapabilities, lp,
new NetworkScore.Builder().setLegacyInt(VPN_DEFAULT_SCORE).build(),
- networkAgentConfig, mNetworkProvider);
+ networkAgentConfig, mNetworkProvider, validationCallback);
final long token = Binder.clearCallingIdentity();
try {
mNetworkAgent.register();
@@ -2723,7 +2758,7 @@
@Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostFuture;
@Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionFuture;
-
+ @Nullable private ScheduledFuture<?> mScheduledHandleDataStallFuture;
/** Signal to ensure shutdown is honored even if a new Network is connected. */
private boolean mIsRunning = true;
@@ -2750,6 +2785,14 @@
private boolean mMobikeEnabled = false;
/**
+ * The number of attempts to reset the IKE session since the last successful connection.
+ *
+ * <p>This variable controls the retry delay, and is reset when the VPN pass network
+ * validation.
+ */
+ private int mDataStallRetryCount = 0;
+
+ /**
* The number of attempts since the last successful connection.
*
* <p>This variable controls the retry delay, and is reset when a new IKE session is
@@ -2931,7 +2974,7 @@
if (isSettingsVpnLocked()) {
prepareStatusIntent();
}
- agentConnect();
+ agentConnect(this::onValidationStatus);
return; // Link properties are already sent.
} else {
// Underlying networks also set in agentConnect()
@@ -3200,18 +3243,52 @@
// Ignore stale runner.
if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return;
- // Handle the report only for current VPN network.
+ // Handle the report only for current VPN network. If data stall is already
+ // reported, ignoring the other reports. It means that the stall is not
+ // recovered by MOBIKE and should be on the way to reset the ike session.
if (mNetworkAgent != null
- && mNetworkAgent.getNetwork().equals(report.getNetwork())) {
+ && mNetworkAgent.getNetwork().equals(report.getNetwork())
+ && !mDataStallSuspected) {
Log.d(TAG, "Data stall suspected");
// Trigger MOBIKE.
maybeMigrateIkeSession(mActiveNetwork);
+ mDataStallSuspected = true;
}
}
}
}
+ public void onValidationStatus(int status) {
+ if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
+ // No data stall now. Reset it.
+ mExecutor.execute(() -> {
+ mDataStallSuspected = false;
+ mDataStallRetryCount = 0;
+ if (mScheduledHandleDataStallFuture != null) {
+ Log.d(TAG, "Recovered from stall. Cancel pending reset action.");
+ mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */);
+ mScheduledHandleDataStallFuture = null;
+ }
+ });
+ } else {
+ // Skip other invalid status if the scheduled recovery exists.
+ if (mScheduledHandleDataStallFuture != null) return;
+
+ mScheduledHandleDataStallFuture = mExecutor.schedule(() -> {
+ if (mDataStallSuspected) {
+ Log.d(TAG, "Reset session to recover stalled network");
+ // This will reset old state if it exists.
+ startIkeSession(mActiveNetwork);
+ }
+
+ // Reset mScheduledHandleDataStallFuture since it's already run on executor
+ // thread.
+ mScheduledHandleDataStallFuture = null;
+ }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS);
+ }
+ }
+
/**
* Handles loss of the default underlying network
*
@@ -4339,6 +4416,7 @@
// un-finalized.
@VisibleForTesting
public static class VpnNetworkAgentWrapper extends NetworkAgent {
+ private final ValidationStatusCallback mCallback;
/** Create an VpnNetworkAgentWrapper */
public VpnNetworkAgentWrapper(
@NonNull Context context,
@@ -4348,8 +4426,10 @@
@NonNull LinkProperties lp,
@NonNull NetworkScore score,
@NonNull NetworkAgentConfig config,
- @Nullable NetworkProvider provider) {
+ @Nullable NetworkProvider provider,
+ @Nullable ValidationStatusCallback callback) {
super(context, looper, logTag, nc, lp, score, config, provider);
+ mCallback = callback;
}
/** Update the LinkProperties */
@@ -4371,6 +4451,13 @@
public void onNetworkUnwanted() {
// We are user controlled, not driven by NetworkRequest.
}
+
+ @Override
+ public void onValidationStatus(int status, Uri redirectUri) {
+ if (mCallback != null) {
+ mCallback.onValidationStatus(status);
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 5c679b8..9c1cf38 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -51,8 +51,6 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
@@ -60,6 +58,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IntPair;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
index 7c9a484..3581981 100644
--- a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
+++ b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
@@ -22,12 +22,12 @@
import android.os.SystemClock;
import android.os.UserManager;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 1686cb2..e9856d0 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -55,8 +55,6 @@
import android.provider.Settings;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.Display;
@@ -64,6 +62,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.RingBuffer;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 7e80b7d..e907ebf 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -127,6 +127,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
@@ -151,6 +152,7 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
+
/**
* Manages attached displays.
* <p>
@@ -1900,6 +1902,14 @@
if (displayDevice == null) {
return;
}
+ if (mLogicalDisplayMapper.getDisplayLocked(displayDevice)
+ .getDisplayInfoLocked().type == Display.TYPE_INTERNAL) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BRIGHTNESS_CONFIGURATION_UPDATED,
+ c.getCurve().first,
+ c.getCurve().second,
+ // should not be logged for virtual displays
+ uniqueId);
+ }
mPersistentDataStore.setBrightnessConfigurationForDisplayLocked(c, displayDevice,
userSerial, packageName);
} finally {
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index a11f172..f30a84f 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -26,14 +26,14 @@
import android.util.SparseArray;
import android.util.SparseLongArray;
import android.util.TimeUtils;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index 3fecef7..88145bd 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -34,10 +34,10 @@
import android.text.FontConfig;
import android.util.IndentingPrintWriter;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
import com.android.internal.util.DumpUtils;
+import com.android.modules.utils.TypedXmlPullParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
index 15abbd5..fd00980 100644
--- a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
+++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
@@ -21,10 +21,11 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index ba19cf0..573bf19 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -25,11 +25,11 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.modules.utils.TypedXmlPullParser;
import com.android.server.hdmi.Constants.AbortReason;
import com.android.server.hdmi.Constants.AudioCodec;
import com.android.server.hdmi.Constants.FeatureOpcode;
diff --git a/services/core/java/com/android/server/input/ConfigurationProcessor.java b/services/core/java/com/android/server/input/ConfigurationProcessor.java
index 0563806..b6953a3 100644
--- a/services/core/java/com/android/server/input/ConfigurationProcessor.java
+++ b/services/core/java/com/android/server/input/ConfigurationProcessor.java
@@ -18,11 +18,11 @@
import android.text.TextUtils;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
import java.io.InputStream;
import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 5513cd6..1bb10c7 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -21,8 +21,6 @@
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseIntArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.Surface;
@@ -34,6 +32,9 @@
import org.xmlpull.v1.XmlPullParserException;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
index 816d08a..8e6452b 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
@@ -25,12 +25,13 @@
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
index ab91290..e831e40 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
@@ -17,9 +17,9 @@
package com.android.server.integrity.parser;
import android.annotation.Nullable;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlPullParser;
import com.android.server.integrity.model.RuleMetadata;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
index 7aed352..022b4b8 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
@@ -19,9 +19,9 @@
import static com.android.server.integrity.parser.RuleMetadataParser.RULE_PROVIDER_TAG;
import static com.android.server.integrity.parser.RuleMetadataParser.VERSION_TAG;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.integrity.model.RuleMetadata;
import org.xmlpull.v1.XmlSerializer;
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 37a4869..67c931f 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -37,12 +37,12 @@
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
index d13b1f4..215c653 100644
--- a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
+++ b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
@@ -28,12 +28,12 @@
import android.text.TextUtils;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java
index 0c209c5..2596cee 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java
@@ -45,9 +45,10 @@
import android.security.keystore.recovery.KeyDerivationParams;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.util.Base64;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlPullParser;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java
index eb34e98..9e3da23 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java
@@ -46,9 +46,10 @@
import android.security.keystore.recovery.KeyDerivationParams;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.util.Base64;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlSerializer;
+
import java.io.IOException;
import java.io.OutputStream;
import java.security.cert.CertPath;
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 77dbde1..f653b93 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1662,116 +1662,75 @@
}
private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
- MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
-
+ MediaRoute2ProviderInfo newInfo = provider.getProviderInfo();
int providerInfoIndex =
indexOfRouteProviderInfoByUniqueId(provider.getUniqueId(), mLastProviderInfos);
-
- MediaRoute2ProviderInfo prevInfo =
+ MediaRoute2ProviderInfo oldInfo =
providerInfoIndex == -1 ? null : mLastProviderInfos.get(providerInfoIndex);
-
- // Ignore if no changes
- if (Objects.equals(prevInfo, currentInfo)) {
+ if (oldInfo == newInfo) {
+ // Nothing to do.
return;
}
- boolean hasAddedOrModifiedRoutes = false;
- boolean hasRemovedRoutes = false;
-
- boolean isSystemProvider = provider.mIsSystemRouteProvider;
-
- if (prevInfo == null) {
- // Provider is being added.
- mLastProviderInfos.add(currentInfo);
- addToRoutesMap(currentInfo.getRoutes(), isSystemProvider);
- // Check if new provider exposes routes.
- hasAddedOrModifiedRoutes = !currentInfo.getRoutes().isEmpty();
- } else if (currentInfo == null) {
- // Provider is being removed.
- hasRemovedRoutes = true;
- mLastProviderInfos.remove(prevInfo);
- removeFromRoutesMap(prevInfo.getRoutes(), isSystemProvider);
- } else {
- // Provider is being updated.
- mLastProviderInfos.set(providerInfoIndex, currentInfo);
- final Collection<MediaRoute2Info> currentRoutes = currentInfo.getRoutes();
-
- // Checking for individual routes.
- for (MediaRoute2Info route : currentRoutes) {
- if (!route.isValid()) {
- Slog.w(
- TAG,
- "onProviderStateChangedOnHandler: Ignoring invalid route : "
- + route);
- continue;
- }
-
- MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
- if (prevRoute == null || !Objects.equals(prevRoute, route)) {
- hasAddedOrModifiedRoutes = true;
- mLastNotifiedRoutesToPrivilegedRouters.put(route.getId(), route);
- if (!isSystemProvider) {
- mLastNotifiedRoutesToNonPrivilegedRouters.put(route.getId(), route);
- }
- }
+ Collection<MediaRoute2Info> newRoutes;
+ Set<String> newRouteIds;
+ if (newInfo != null) {
+ // Adding or updating a provider.
+ newRoutes = newInfo.getRoutes();
+ newRouteIds =
+ newRoutes.stream().map(MediaRoute2Info::getId).collect(Collectors.toSet());
+ if (providerInfoIndex >= 0) {
+ mLastProviderInfos.set(providerInfoIndex, newInfo);
+ } else {
+ mLastProviderInfos.add(newInfo);
}
+ } else /* newInfo == null */ {
+ // Removing a provider.
+ mLastProviderInfos.remove(oldInfo);
+ newRouteIds = Collections.emptySet();
+ newRoutes = Collections.emptySet();
+ }
- // Checking for individual removals
- for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) {
- if (currentInfo.getRoute(prevRoute.getOriginalId()) == null) {
- hasRemovedRoutes = true;
- mLastNotifiedRoutesToPrivilegedRouters.remove(prevRoute.getId());
- if (!isSystemProvider) {
- mLastNotifiedRoutesToNonPrivilegedRouters.remove(prevRoute.getId());
- }
- }
+ // Add new routes to the maps.
+ boolean hasAddedOrModifiedRoutes = false;
+ for (MediaRoute2Info newRouteInfo : newRoutes) {
+ if (!newRouteInfo.isValid()) {
+ Slog.w(TAG, "onProviderStateChangedOnHandler: Ignoring invalid route : "
+ + newRouteInfo);
+ continue;
+ }
+ if (!provider.mIsSystemRouteProvider) {
+ mLastNotifiedRoutesToNonPrivilegedRouters.put(
+ newRouteInfo.getId(), newRouteInfo);
+ }
+ MediaRoute2Info oldRouteInfo =
+ mLastNotifiedRoutesToPrivilegedRouters.put(
+ newRouteInfo.getId(), newRouteInfo);
+ hasAddedOrModifiedRoutes |=
+ oldRouteInfo == null || !oldRouteInfo.equals(newRouteInfo);
+ }
+
+ // Remove stale routes from the maps.
+ Collection<MediaRoute2Info> oldRoutes =
+ oldInfo == null ? Collections.emptyList() : oldInfo.getRoutes();
+ boolean hasRemovedRoutes = false;
+ for (MediaRoute2Info oldRoute : oldRoutes) {
+ String oldRouteId = oldRoute.getId();
+ if (!newRouteIds.contains(oldRouteId)) {
+ hasRemovedRoutes = true;
+ mLastNotifiedRoutesToPrivilegedRouters.remove(oldRouteId);
+ mLastNotifiedRoutesToNonPrivilegedRouters.remove(oldRouteId);
}
}
dispatchUpdates(
hasAddedOrModifiedRoutes,
hasRemovedRoutes,
- isSystemProvider,
+ provider.mIsSystemRouteProvider,
mSystemProvider.getDefaultRoute());
}
/**
- * Adds provided routes to {@link #mLastNotifiedRoutesToPrivilegedRouters}. Also adds them
- * to {@link #mLastNotifiedRoutesToNonPrivilegedRouters} if they were provided by a
- * non-system route provider. Overwrites any route with matching id that already exists.
- *
- * @param routes list of routes to be added.
- * @param isSystemRoutes indicates whether routes come from a system route provider.
- */
- private void addToRoutesMap(
- @NonNull Collection<MediaRoute2Info> routes, boolean isSystemRoutes) {
- for (MediaRoute2Info route : routes) {
- if (!isSystemRoutes) {
- mLastNotifiedRoutesToNonPrivilegedRouters.put(route.getId(), route);
- }
- mLastNotifiedRoutesToPrivilegedRouters.put(route.getId(), route);
- }
- }
-
- /**
- * Removes provided routes from {@link #mLastNotifiedRoutesToPrivilegedRouters}. Also
- * removes them from {@link #mLastNotifiedRoutesToNonPrivilegedRouters} if they were
- * provided by a non-system route provider.
- *
- * @param routes list of routes to be removed.
- * @param isSystemRoutes whether routes come from a system route provider.
- */
- private void removeFromRoutesMap(
- @NonNull Collection<MediaRoute2Info> routes, boolean isSystemRoutes) {
- for (MediaRoute2Info route : routes) {
- if (!isSystemRoutes) {
- mLastNotifiedRoutesToNonPrivilegedRouters.remove(route.getId());
- }
- mLastNotifiedRoutesToPrivilegedRouters.remove(route.getId());
- }
- }
-
- /**
* Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters}
* and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link
* android.media.MediaRouter2 routers} and {@link MediaRouter2Manager managers} after a call
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index d770f71..56f3296 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -239,8 +239,6 @@
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.SparseSetArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.R;
@@ -255,6 +253,8 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.StatLogger;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.net.module.util.NetworkIdentityUtils;
import com.android.net.module.util.NetworkStatsUtils;
import com.android.net.module.util.PermissionUtils;
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
index 4506b7d..7da78f3 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
@@ -20,14 +20,14 @@
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.HexDump;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 3238f1f..3329f54 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -36,10 +36,10 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
-import android.util.TypedXmlSerializer;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.notification.NotificationManagerService.DumpFilter;
import java.io.IOException;
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 4d55d4e..004caf3 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -60,14 +60,14 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.notification.NotificationManagerService.DumpFilter;
import com.android.server.utils.TimingsTraceAndSlog;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f459c0e..eb37ceb 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -258,8 +258,6 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.StatsEvent;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import android.view.accessibility.AccessibilityEvent;
@@ -291,6 +289,8 @@
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.internal.widget.LockPatternUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.DeviceIdleInternal;
import com.android.server.EventLogTags;
import com.android.server.IoThread;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index d8aa469..bbbf452 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -63,8 +63,6 @@
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.StatsEvent;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
@@ -73,6 +71,8 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.notification.PermissionHelper.PackagePermission;
import org.json.JSONArray;
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 61936df..4bbd40d 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -30,12 +30,12 @@
import android.util.IntArray;
import android.util.Log;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.PackageManagerService;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 4c23ab8..4b2c88c 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -72,8 +72,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.StatsEvent;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
@@ -82,6 +80,8 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 9e39226..eae614a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -28,14 +28,14 @@
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index bb918d5..5e98cc0 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -35,9 +35,10 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.TypedValue;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlPullParser;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 4e9c472..d6233c7 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -148,9 +148,18 @@
+ intent.toShortString(false, true, false, false)
+ " " + intent.getExtras(), here);
}
+ final boolean ordered;
+ if (mAmInternal.isModernQueueEnabled()) {
+ // When the modern broadcast stack is enabled, deliver all our
+ // broadcasts as unordered, since the modern stack has better
+ // support for sequencing cold-starts, and it supports
+ // delivering resultTo for non-ordered broadcasts
+ ordered = false;
+ } else {
+ ordered = (finishedReceiver != null);
+ }
mAmInternal.broadcastIntent(
- intent, finishedReceiver, requiredPermissions,
- finishedReceiver != null, userId,
+ intent, finishedReceiver, requiredPermissions, ordered, userId,
broadcastAllowList == null ? null : broadcastAllowList.get(userId),
filterExtrasForReceiver, bOptions);
}
diff --git a/services/core/java/com/android/server/pm/CommitRequest.java b/services/core/java/com/android/server/pm/CommitRequest.java
deleted file mode 100644
index d1a6002..0000000
--- a/services/core/java/com/android/server/pm/CommitRequest.java
+++ /dev/null
@@ -1,35 +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.server.pm;
-
-import android.annotation.NonNull;
-
-import java.util.Map;
-
-/**
- * Package state to commit to memory and disk after reconciliation has completed.
- */
-final class CommitRequest {
- final Map<String, ReconciledPackage> mReconciledPackages;
- @NonNull final int[] mAllUsers;
-
- CommitRequest(Map<String, ReconciledPackage> reconciledPackages,
- @NonNull int[] allUsers) {
- mReconciledPackages = reconciledPackages;
- mAllUsers = allUsers;
- }
-}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 86b8272..b9967f9 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -114,7 +114,6 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
@@ -123,6 +122,7 @@
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.parsing.PackageInfoUtils;
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
index 718756f..0cd698a 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
@@ -21,10 +21,10 @@
import android.content.IntentFilter;
import android.os.UserHandle;
import android.util.Log;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.utils.SnapshotCache;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index f6472a7..6f59096 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -352,8 +352,7 @@
}
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
- private void scanDirTracedLI(File scanDir,
- int parseFlags, int scanFlags,
+ private void scanDirTracedLI(File scanDir, int parseFlags, int scanFlags,
PackageParser2 packageParser, ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 30ecc1c..9d007c9 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -245,50 +245,42 @@
@GuardedBy("mPm.mLock")
public AndroidPackage commitReconciledScanResultLocked(
@NonNull ReconciledPackage reconciledPkg, int[] allUsers) {
- final ScanResult result = reconciledPkg.mScanResult;
- final ScanRequest request = result.mRequest;
+ final InstallRequest request = reconciledPkg.mInstallRequest;
// TODO(b/135203078): Move this even further away
- ParsedPackage parsedPackage = request.mParsedPackage;
- if ("android".equals(parsedPackage.getPackageName())) {
+ ParsedPackage parsedPackage = request.getParsedPackage();
+ if (parsedPackage != null && "android".equals(parsedPackage.getPackageName())) {
// TODO(b/135203078): Move this to initial parse
parsedPackage.setVersionCode(mPm.getSdkVersion())
.setVersionCodeMajor(0);
}
- final AndroidPackage oldPkg = request.mOldPkg;
- final @ParsingPackageUtils.ParseFlags int parseFlags = request.mParseFlags;
- final @PackageManagerService.ScanFlags int scanFlags = request.mScanFlags;
- final PackageSetting oldPkgSetting = request.mOldPkgSetting;
- final PackageSetting originalPkgSetting = request.mOriginalPkgSetting;
- final UserHandle user = request.mUser;
- final String realPkgName = request.mRealPkgName;
- final List<String> changedAbiCodePath = result.mChangedAbiCodePath;
+ final @PackageManagerService.ScanFlags int scanFlags = request.getScanFlags();
+ final PackageSetting oldPkgSetting = request.getScanRequestOldPackageSetting();
+ final PackageSetting originalPkgSetting = request.getScanRequestOriginalPackageSetting();
+ final String realPkgName = request.getRealPackageName();
+ final List<String> changedAbiCodePath = request.getChangedAbiCodePath();
final PackageSetting pkgSetting;
- if (request.mPkgSetting != null) {
+ if (request.getScanRequestPackageSetting() != null) {
SharedUserSetting requestSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(
- request.mPkgSetting);
+ request.getScanRequestPackageSetting());
SharedUserSetting resultSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(
- result.mPkgSetting);
+ request.getScanRequestPackageSetting());
if (requestSharedUserSetting != null
&& requestSharedUserSetting != resultSharedUserSetting) {
// shared user changed, remove from old shared user
- requestSharedUserSetting.removePackage(request.mPkgSetting);
+ requestSharedUserSetting.removePackage(request.getScanRequestPackageSetting());
// Prune unused SharedUserSetting
if (mPm.mSettings.checkAndPruneSharedUserLPw(requestSharedUserSetting, false)) {
// Set the app ID in removed info for UID_REMOVED broadcasts
- if (reconciledPkg.mInstallRequest != null
- && reconciledPkg.mInstallRequest.getRemovedInfo() != null) {
- reconciledPkg.mInstallRequest.getRemovedInfo().mRemovedAppId =
- requestSharedUserSetting.mAppId;
- }
+ request.setRemovedAppId(requestSharedUserSetting.mAppId);
}
}
}
- if (result.mExistingSettingCopied) {
- pkgSetting = request.mPkgSetting;
- pkgSetting.updateFrom(result.mPkgSetting);
+ if (request.isExistingSettingCopied()) {
+ pkgSetting = request.getScanRequestPackageSetting();
+ pkgSetting.updateFrom(request.getScannedPackageSetting());
} else {
- pkgSetting = result.mPkgSetting;
+ pkgSetting = request.getScannedPackageSetting();
if (originalPkgSetting != null) {
mPm.mSettings.addRenamedPackageLPw(
AndroidPackageUtils.getRealPackageOrNull(parsedPackage),
@@ -308,26 +300,23 @@
mPm.mSettings.convertSharedUserSettingsLPw(sharedUserSetting);
}
}
- if (reconciledPkg.mInstallRequest != null
- && reconciledPkg.mInstallRequest.isForceQueryableOverride()) {
+ if (request.isForceQueryableOverride()) {
pkgSetting.setForceQueryableOverride(true);
}
// If this is part of a standard install, set the initiating package name, else rely on
// previous device state.
- if (reconciledPkg.mInstallRequest != null) {
- InstallSource installSource = reconciledPkg.mInstallRequest.getInstallSource();
- if (installSource != null) {
- if (installSource.initiatingPackageName != null) {
- final PackageSetting ips = mPm.mSettings.getPackageLPr(
- installSource.initiatingPackageName);
- if (ips != null) {
- installSource = installSource.setInitiatingPackageSignatures(
- ips.getSignatures());
- }
+ InstallSource installSource = request.getInstallSource();
+ if (installSource != null) {
+ if (installSource.initiatingPackageName != null) {
+ final PackageSetting ips = mPm.mSettings.getPackageLPr(
+ installSource.initiatingPackageName);
+ if (ips != null) {
+ installSource = installSource.setInitiatingPackageSignatures(
+ ips.getSignatures());
}
- pkgSetting.setInstallSource(installSource);
}
+ pkgSetting.setInstallSource(installSource);
}
if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
@@ -382,10 +371,9 @@
}
}
- final int userId = user == null ? 0 : user.getIdentifier();
+ final int userId = request.getUserId();
// Modify state for the given package setting
- commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags,
- (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
+ commitPackageSettings(pkg, pkgSetting, oldPkgSetting, reconciledPkg);
if (pkgSetting.getInstantApp(userId)) {
mPm.mInstantAppRegistry.addInstantApp(userId, pkgSetting.getAppId());
}
@@ -401,11 +389,14 @@
* Adds a scanned package to the system. When this method is finished, the package will
* be available for query, resolution, etc...
*/
- private void commitPackageSettings(@NonNull AndroidPackage pkg, @Nullable AndroidPackage oldPkg,
+ private void commitPackageSettings(@NonNull AndroidPackage pkg,
@NonNull PackageSetting pkgSetting, @Nullable PackageSetting oldPkgSetting,
- final @PackageManagerService.ScanFlags int scanFlags, boolean chatty,
ReconciledPackage reconciledPkg) {
final String pkgName = pkg.getPackageName();
+ final InstallRequest request = reconciledPkg.mInstallRequest;
+ final AndroidPackage oldPkg = request.getScanRequestOldPackage();
+ final int scanFlags = request.getScanFlags();
+ final boolean chatty = (request.getParseFlags() & ParsingPackageUtils.PARSE_CHATTY) != 0;
if (mPm.mCustomResolverComponentName != null
&& mPm.mCustomResolverComponentName.getPackageName().equals(pkg.getPackageName())) {
mPm.setUpCustomResolverActivity(pkg, pkgSetting);
@@ -421,9 +412,7 @@
reconciledPkg.mAllowedSharedLibraryInfos,
reconciledPkg.getCombinedAvailablePackages(), scanFlags);
- if (reconciledPkg.mInstallRequest != null) {
- reconciledPkg.mInstallRequest.setLibraryConsumers(clientLibPkgs);
- }
+ request.setLibraryConsumers(clientLibPkgs);
if ((scanFlags & SCAN_BOOTING) != 0) {
// No apps can run during boot scan, so they don't need to be frozen
@@ -438,8 +427,7 @@
mPm.snapshotComputer().checkPackageFrozen(pkgName);
}
- final boolean isReplace =
- reconciledPkg.mPrepareResult != null && reconciledPkg.mPrepareResult.mReplace;
+ final boolean isReplace = request.isReplace();
// Also need to kill any apps that are dependent on the library, except the case of
// installation of new version static shared library.
if (clientLibPkgs != null) {
@@ -705,6 +693,9 @@
* Returns whether the restore successfully completed.
*/
private boolean performBackupManagerRestore(int userId, int token, InstallRequest request) {
+ if (request.getPkg() == null) {
+ return false;
+ }
IBackupManager iBackupManager = mInjector.getIBackupManager();
if (iBackupManager != null) {
// For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
@@ -743,6 +734,9 @@
* Returns whether the restore successfully completed.
*/
private boolean performRollbackManagerRestore(int userId, int token, InstallRequest request) {
+ if (request.getPkg() == null) {
+ return false;
+ }
final String packageName = request.getPkg().getPackageName();
final int[] allUsers = mPm.mUserManager.getUserIds();
final int[] installedUsers;
@@ -810,22 +804,16 @@
*/
@GuardedBy("mPm.mInstallLock")
private void installPackagesLI(List<InstallRequest> requests) {
- final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size());
- final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size());
- final Map<String, InstallRequest> installRequests = new ArrayMap<>(requests.size());
+ final Set<String> scannedPackages = new ArraySet<>(requests.size());
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
boolean success = false;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");
for (InstallRequest request : requests) {
- // TODO(b/109941548): remove this once we've pulled everything from it and into
- // scan, reconcile or commit.
- final PrepareResult prepareResult;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
- prepareResult =
- preparePackageLI(request);
+ preparePackageLI(request);
} catch (PrepareFailure prepareFailure) {
request.setError(prepareFailure.error,
prepareFailure.getMessage());
@@ -835,28 +823,31 @@
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
- request.setInstallerPackageName(request.getSourceInstallerPackageName());
- final String packageName = prepareResult.mPackageToScan.getPackageName();
- prepareResults.put(packageName, prepareResult);
- installRequests.put(packageName, request);
+ final ParsedPackage packageToScan = request.getParsedPackage();
+ if (packageToScan == null) {
+ request.setError(INSTALL_FAILED_SESSION_INVALID,
+ "Failed to obtain package to scan");
+ return;
+ }
+ request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
+ final String packageName = packageToScan.getPackageName();
try {
- final ScanResult result = scanPackageTracedLI(
- prepareResult.mPackageToScan, prepareResult.mParseFlags,
- prepareResult.mScanFlags, System.currentTimeMillis(),
- request.getUser(), request.getAbiOverride());
- if (null != preparedScans.put(result.mPkgSetting.getPkg().getPackageName(),
- result)) {
+ final ScanResult scanResult = scanPackageTracedLI(request.getParsedPackage(),
+ request.getParseFlags(), request.getScanFlags(),
+ System.currentTimeMillis(), request.getUser(),
+ request.getAbiOverride());
+ request.setScanResult(scanResult);
+ if (!scannedPackages.add(packageName)) {
request.setError(
PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
"Duplicate package "
- + result.mPkgSetting.getPkg().getPackageName()
+ + packageName
+ " in multi-package install request.");
return;
}
if (!checkNoAppStorageIsConsistent(
- result.mRequest.mOldPkg, result.mPkgSetting.getPkg())) {
+ request.getScanRequestOldPackage(), packageToScan)) {
// TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible
// signatures. Is there a better error code?
request.setError(
@@ -865,31 +856,28 @@
+ PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
return;
}
- final boolean isApex = (result.mRequest.mScanFlags & SCAN_AS_APEX) != 0;
+ final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
if (!isApex) {
- createdAppId.put(packageName, optimisticallyRegisterAppId(result));
+ createdAppId.put(packageName, optimisticallyRegisterAppId(request));
} else {
- result.mPkgSetting.setAppId(Process.INVALID_UID);
+ request.getScannedPackageSetting().setAppId(Process.INVALID_UID);
}
- versionInfos.put(result.mPkgSetting.getPkg().getPackageName(),
- mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg()));
+ versionInfos.put(packageName,
+ mPm.getSettingsVersionForPackage(packageToScan));
} catch (PackageManagerException e) {
request.setError("Scanning Failed.", e);
return;
}
}
- CommitRequest commitRequest;
+ Map<String, ReconciledPackage> reconciledPackages;
synchronized (mPm.mLock) {
- ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans,
- installRequests, prepareResults,
- Collections.unmodifiableMap(mPm.mPackages), versionInfos);
- Map<String, ReconciledPackage> reconciledPackages;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
reconciledPackages = ReconcilePackageUtils.reconcilePackages(
- reconcileRequest, mSharedLibraries,
- mPm.mSettings.getKeySetManagerService(), mPm.mSettings);
+ requests, Collections.unmodifiableMap(mPm.mPackages),
+ versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
+ mPm.mSettings);
} catch (ReconcileFailure e) {
for (InstallRequest request : requests) {
request.setError("Reconciliation failed...", e);
@@ -900,15 +888,13 @@
}
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "commitPackages");
- commitRequest = new CommitRequest(reconciledPackages,
- mPm.mUserManager.getUserIds());
- commitPackagesLocked(commitRequest);
+ commitPackagesLocked(reconciledPackages, mPm.mUserManager.getUserIds());
success = true;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
- executePostCommitStepsLIF(commitRequest);
+ executePostCommitStepsLIF(reconciledPackages);
} finally {
if (success) {
for (InstallRequest request : requests) {
@@ -932,10 +918,10 @@
request.getDataLoaderType(), request.getUser(), mContext);
}
} else {
- for (ScanResult result : preparedScans.values()) {
- if (createdAppId.getOrDefault(result.mRequest.mParsedPackage.getPackageName(),
- false)) {
- cleanUpAppIdCreation(result);
+ for (InstallRequest installRequest : requests) {
+ if (installRequest.getParsedPackage() != null && createdAppId.getOrDefault(
+ installRequest.getParsedPackage().getPackageName(), false)) {
+ cleanUpAppIdCreation(installRequest);
}
}
// TODO(b/194319951): create a more descriptive reason than unknown
@@ -968,8 +954,7 @@
}
@GuardedBy("mPm.mInstallLock")
- private PrepareResult preparePackageLI(InstallRequest request)
- throws PrepareFailure {
+ private void preparePackageLI(InstallRequest request) throws PrepareFailure {
final int installFlags = request.getInstallFlags();
final boolean onExternal = request.getVolumeUuid() != null;
final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0);
@@ -1550,8 +1535,7 @@
// don't allow an upgrade from full to ephemeral
if (isInstantApp) {
- if (request.getUser() == null
- || request.getUserId() == UserHandle.USER_ALL) {
+ if (request.getUserId() == UserHandle.USER_ALL) {
for (int currentUser : allUsers) {
if (!ps.getInstantApp(currentUser)) {
// can't downgrade from full to instant
@@ -1624,7 +1608,6 @@
targetParseFlags = systemParseFlags;
targetScanFlags = systemScanFlags;
} else { // non system replace
- replace = true;
if (DEBUG_INSTALL) {
Slog.d(TAG,
"replaceNonSystemPackageLI: new=" + parsedPackage + ", old="
@@ -1634,7 +1617,6 @@
} else { // new package install
ps = null;
disabledPs = null;
- replace = false;
oldPackage = null;
// Remember this for later, in case we need to rollback this install
String pkgName1 = parsedPackage.getPackageName();
@@ -1665,7 +1647,7 @@
// we're passing the freezer back to be closed in a later phase of install
shouldCloseFreezerBeforeReturn = false;
- return new PrepareResult(replace, targetScanFlags, targetParseFlags,
+ request.setPrepareResult(replace, targetScanFlags, targetParseFlags,
oldPackage, parsedPackage, replace /* clearCodeCache */, sysPkg,
ps, disabledPs);
} finally {
@@ -1894,19 +1876,18 @@
}
@GuardedBy("mPm.mLock")
- private void commitPackagesLocked(final CommitRequest request) {
+ private void commitPackagesLocked(Map<String, ReconciledPackage> reconciledPackages,
+ @NonNull int[] allUsers) {
// TODO: remove any expected failures from this method; this should only be able to fail due
// to unavoidable errors (I/O, etc.)
- for (ReconciledPackage reconciledPkg : request.mReconciledPackages.values()) {
- final ScanResult scanResult = reconciledPkg.mScanResult;
- final ScanRequest scanRequest = scanResult.mRequest;
- final ParsedPackage parsedPackage = scanRequest.mParsedPackage;
- final String packageName = parsedPackage.getPackageName();
+ for (ReconciledPackage reconciledPkg : reconciledPackages.values()) {
final InstallRequest installRequest = reconciledPkg.mInstallRequest;
+ final ParsedPackage parsedPackage = installRequest.getParsedPackage();
+ final String packageName = parsedPackage.getPackageName();
final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
- if (reconciledPkg.mPrepareResult.mReplace) {
+ if (installRequest.isReplace()) {
AndroidPackage oldPackage = mPm.mPackages.get(packageName);
// Set the update and install times
@@ -1914,15 +1895,16 @@
.getPackageStateInternal(oldPackage.getPackageName());
// TODO(b/225756739): For rebootless APEX, consider using lastUpdateMillis provided
// by apexd to be more accurate.
- reconciledPkg.mPkgSetting
- .setFirstInstallTimeFromReplaced(deletedPkgSetting, request.mAllUsers)
- .setLastUpdateTime(System.currentTimeMillis());
+ installRequest.setScannedPackageSettingFirstInstallTimeFromReplaced(
+ deletedPkgSetting, allUsers);
+ installRequest.setScannedPackageSettingLastUpdateTime(
+ System.currentTimeMillis());
installRequest.getRemovedInfo().mBroadcastAllowList =
mPm.mAppsFilter.getVisibilityAllowList(mPm.snapshotComputer(),
- reconciledPkg.mPkgSetting, request.mAllUsers,
- mPm.mSettings.getPackagesLocked());
- if (reconciledPkg.mPrepareResult.mSystem) {
+ installRequest.getScannedPackageSetting(),
+ allUsers, mPm.mSettings.getPackagesLocked());
+ if (installRequest.isSystem()) {
// Remove existing system package
removePackageHelper.removePackage(oldPackage, true);
if (!disableSystemPackageLPw(oldPackage)) {
@@ -1942,7 +1924,7 @@
// Settings will be written during the call to updateSettingsLI().
deletePackageHelper.executeDeletePackage(
reconciledPkg.mDeletePackageAction, packageName,
- true, request.mAllUsers, false);
+ true, allUsers, false);
} catch (SystemDeleteException e) {
if (mPm.mIsEngBuild) {
throw new RuntimeException("Unexpected failure", e);
@@ -1952,7 +1934,7 @@
// Successfully deleted the old package; proceed with replace.
// Update the in-memory copy of the previous code paths.
PackageSetting ps1 = mPm.mSettings.getPackageLPr(
- reconciledPkg.mPrepareResult.mExistingPackage.getPackageName());
+ installRequest.getExistingPackageName());
if ((installRequest.getInstallFlags() & PackageManager.DONT_KILL_APP)
== 0) {
Set<String> oldCodePaths = ps1.getOldCodePaths();
@@ -1977,9 +1959,8 @@
}
}
- AndroidPackage pkg = commitReconciledScanResultLocked(
- reconciledPkg, request.mAllUsers);
- updateSettingsLI(pkg, reconciledPkg, request.mAllUsers, installRequest);
+ AndroidPackage pkg = commitReconciledScanResultLocked(reconciledPkg, allUsers);
+ updateSettingsLI(pkg, allUsers, installRequest);
final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
if (ps != null) {
@@ -2000,12 +1981,12 @@
return mPm.mSettings.disableSystemPackageLPw(oldPkg.getPackageName(), true);
}
- private void updateSettingsLI(AndroidPackage newPackage, ReconciledPackage reconciledPkg,
+ private void updateSettingsLI(AndroidPackage newPackage,
int[] allUsers, InstallRequest installRequest) {
- updateSettingsInternalLI(newPackage, reconciledPkg, allUsers, installRequest);
+ updateSettingsInternalLI(newPackage, allUsers, installRequest);
}
- private void updateSettingsInternalLI(AndroidPackage pkg, ReconciledPackage reconciledPkg,
+ private void updateSettingsInternalLI(AndroidPackage pkg,
int[] allUsers, InstallRequest installRequest) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
@@ -2175,8 +2156,7 @@
}
final int autoRevokePermissionsMode = installRequest.getAutoRevokePermissionsMode();
permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode);
- final ScanResult scanResult = reconciledPkg.mScanResult;
- mPm.mPermissionManager.onPackageInstalled(pkg, scanResult.mPreviousAppId,
+ mPm.mPermissionManager.onPackageInstalled(pkg, installRequest.getPreviousAppId(),
permissionParamsBuilder.build(), userId);
// Apply restricted settings on potentially dangerous packages.
if (installRequest.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
@@ -2216,14 +2196,13 @@
* locks on {@link com.android.server.pm.PackageManagerService.mLock}.
*/
@GuardedBy("mPm.mInstallLock")
- private void executePostCommitStepsLIF(CommitRequest commitRequest) {
+ private void executePostCommitStepsLIF(Map<String, ReconciledPackage> reconciledPackages) {
final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
- for (ReconciledPackage reconciledPkg : commitRequest.mReconciledPackages.values()) {
- final boolean instantApp = ((reconciledPkg.mScanResult.mRequest.mScanFlags
- & SCAN_AS_INSTANT_APP) != 0);
- final boolean isApex = ((reconciledPkg.mScanResult.mRequest.mScanFlags
- & SCAN_AS_APEX) != 0);
- final AndroidPackage pkg = reconciledPkg.mPkgSetting.getPkg();
+ for (ReconciledPackage reconciledPkg : reconciledPackages.values()) {
+ final InstallRequest installRequest = reconciledPkg.mInstallRequest;
+ final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
+ final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
+ final AndroidPackage pkg = installRequest.getScannedPackageSetting().getPkg();
final String packageName = pkg.getPackageName();
final String codePath = pkg.getPath();
final boolean onIncremental = mIncrementalManager != null
@@ -2238,12 +2217,12 @@
}
// Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0);
- if (reconciledPkg.mPrepareResult.mClearCodeCache) {
+ if (installRequest.isClearCodeCache()) {
mAppDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
| Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
}
- if (reconciledPkg.mPrepareResult.mReplace) {
+ if (installRequest.isReplace()) {
mDexManager.notifyPackageUpdated(pkg.getPackageName(),
pkg.getBaseApkPath(), pkg.getSplitCodePaths());
}
@@ -2252,14 +2231,13 @@
// This needs to be done before invoking dexopt so that any install-time profile
// can be used for optimizations.
mArtManagerService.prepareAppProfiles(
- pkg,
- mPm.resolveUserIds(reconciledPkg.mInstallRequest.getUserId()),
+ pkg, mPm.resolveUserIds(installRequest.getUserId()),
/* updateReferenceProfileContent= */ true);
// Compute the compilation reason from the installation scenario.
final int compilationReason =
mDexManager.getCompilationReasonForInstallScenario(
- reconciledPkg.mInstallRequest.getInstallScenario());
+ installRequest.getInstallScenario());
// Construct the DexoptOptions early to see if we should skip running dexopt.
//
@@ -2268,10 +2246,8 @@
//
// Also, don't fail application installs if the dexopt step fails.
final boolean isBackupOrRestore =
- reconciledPkg.mInstallRequest.getInstallReason()
- == INSTALL_REASON_DEVICE_RESTORE
- || reconciledPkg.mInstallRequest.getInstallReason()
- == INSTALL_REASON_DEVICE_SETUP;
+ installRequest.getInstallReason() == INSTALL_REASON_DEVICE_RESTORE
+ || installRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP;
final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
| DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
@@ -2323,22 +2299,14 @@
}
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
- ScanResult result = reconciledPkg.mScanResult;
// This mirrors logic from commitReconciledScanResultLocked, where the library files
// needed for dexopt are assigned.
- // TODO: Fix this to have 1 mutable PackageSetting for scan/install. If the previous
- // setting needs to be passed to have a comparison, hide it behind an immutable
- // interface. There's no good reason to have 3 different ways to access the real
- // PackageSetting object, only one of which is actually correct.
- PackageSetting realPkgSetting = result.mExistingSettingCopied
- ? result.mRequest.mPkgSetting : result.mPkgSetting;
- if (realPkgSetting == null) {
- realPkgSetting = reconciledPkg.mPkgSetting;
- }
+ PackageSetting realPkgSetting = installRequest.getRealPackageSetting();
// Unfortunately, the updated system app flag is only tracked on this PackageSetting
- boolean isUpdatedSystemApp = reconciledPkg.mPkgSetting.getPkgState()
+ boolean isUpdatedSystemApp =
+ installRequest.getScannedPackageSetting().getPkgState()
.isUpdatedSystemApp();
realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
@@ -3059,7 +3027,7 @@
final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
removePackageHelper.removePackage(stubPkg, true /*chatty*/);
try {
- return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, null);
+ return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
e);
@@ -3191,7 +3159,7 @@
| ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
@PackageManagerService.ScanFlags int scanFlags = mPm.getSystemPackageScanFlags(codePath);
final AndroidPackage pkg = scanSystemPackageTracedLI(
- codePath, parseFlags, scanFlags, null);
+ codePath, parseFlags, scanFlags);
synchronized (mPm.mLock) {
PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName());
@@ -3371,7 +3339,7 @@
try {
final File codePath = new File(pkg.getPath());
synchronized (mPm.mInstallLock) {
- scanSystemPackageTracedLI(codePath, 0, scanFlags, null);
+ scanSystemPackageTracedLI(codePath, 0, scanFlags);
}
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to parse updated, ex-system package: "
@@ -3521,7 +3489,7 @@
}
try {
addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
- null);
+ new UserHandle(UserHandle.USER_SYSTEM));
} catch (PackageManagerException e) {
errorCode = e.error;
errorMsg = "Failed to scan " + parseResult.scanFile + ": " + e.getMessage();
@@ -3584,7 +3552,7 @@
try {
synchronized (mPm.mInstallLock) {
final AndroidPackage newPkg = scanSystemPackageTracedLI(
- scanFile, reparseFlags, rescanFlags, null);
+ scanFile, reparseFlags, rescanFlags);
// We rescanned a stub, add it to the list of stubbed system packages
if (newPkg.isStub()) {
stubSystemApps.add(packageName);
@@ -3603,10 +3571,10 @@
*/
@GuardedBy("mPm.mInstallLock")
public AndroidPackage scanSystemPackageTracedLI(File scanFile, final int parseFlags,
- int scanFlags, UserHandle user) throws PackageManagerException {
+ int scanFlags) throws PackageManagerException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage [" + scanFile.toString() + "]");
try {
- return scanSystemPackageLI(scanFile, parseFlags, scanFlags, user);
+ return scanSystemPackageLI(scanFile, parseFlags, scanFlags);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -3617,8 +3585,8 @@
* Returns {@code null} in case of errors and the error code is stored in mLastScanError
*/
@GuardedBy("mPm.mInstallLock")
- private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags,
- UserHandle user) throws PackageManagerException {
+ private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags)
+ throws PackageManagerException {
if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
@@ -3634,7 +3602,8 @@
PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
}
- return addForInitLI(parsedPackage, parseFlags, scanFlags, user);
+ return addForInitLI(parsedPackage, parseFlags, scanFlags,
+ new UserHandle(UserHandle.USER_SYSTEM));
}
/**
@@ -3660,33 +3629,32 @@
parsedPackage, parseFlags, scanFlags, user);
final ScanResult scanResult = scanResultPair.first;
boolean shouldHideSystemApp = scanResultPair.second;
- if (scanResult.mSuccess) {
- synchronized (mPm.mLock) {
- boolean appIdCreated = false;
- try {
- final String pkgName = scanResult.mPkgSetting.getPackageName();
- final ReconcileRequest reconcileRequest = new ReconcileRequest(
- Collections.singletonMap(pkgName, scanResult),
- mPm.mPackages,
- Collections.singletonMap(pkgName,
- mPm.getSettingsVersionForPackage(parsedPackage)));
- final Map<String, ReconciledPackage> reconcileResult =
- ReconcilePackageUtils.reconcilePackages(reconcileRequest,
- mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
- mPm.mSettings);
- if ((scanFlags & SCAN_AS_APEX) == 0) {
- appIdCreated = optimisticallyRegisterAppId(scanResult);
- } else {
- scanResult.mPkgSetting.setAppId(Process.INVALID_UID);
- }
- commitReconciledScanResultLocked(reconcileResult.get(pkgName),
- mPm.mUserManager.getUserIds());
- } catch (PackageManagerException e) {
- if (appIdCreated) {
- cleanUpAppIdCreation(scanResult);
- }
- throw e;
+ final InstallRequest installRequest = new InstallRequest(
+ parsedPackage, parseFlags, scanFlags, user, scanResult);
+
+ synchronized (mPm.mLock) {
+ boolean appIdCreated = false;
+ try {
+ final String pkgName = scanResult.mPkgSetting.getPackageName();
+ final Map<String, ReconciledPackage> reconcileResult =
+ ReconcilePackageUtils.reconcilePackages(
+ Collections.singletonList(installRequest),
+ mPm.mPackages, Collections.singletonMap(pkgName,
+ mPm.getSettingsVersionForPackage(parsedPackage)),
+ mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
+ mPm.mSettings);
+ if ((scanFlags & SCAN_AS_APEX) == 0) {
+ appIdCreated = optimisticallyRegisterAppId(installRequest);
+ } else {
+ installRequest.setScannedPackageSettingAppId(Process.INVALID_UID);
}
+ commitReconciledScanResultLocked(reconcileResult.get(pkgName),
+ mPm.mUserManager.getUserIds());
+ } catch (PackageManagerException e) {
+ if (appIdCreated) {
+ cleanUpAppIdCreation(installRequest);
+ }
+ throw e;
}
}
@@ -3711,13 +3679,14 @@
* @return {@code true} if a new app ID was registered and will need to be cleaned up on
* failure.
*/
- private boolean optimisticallyRegisterAppId(@NonNull ScanResult result)
+ private boolean optimisticallyRegisterAppId(@NonNull InstallRequest installRequest)
throws PackageManagerException {
- if (!result.mExistingSettingCopied || result.needsNewAppId()) {
+ if (!installRequest.isExistingSettingCopied() || installRequest.needsNewAppId()) {
synchronized (mPm.mLock) {
// THROWS: when we can't allocate a user id. add call to check if there's
// enough space to ensure we won't throw; otherwise, don't modify state
- return mPm.mSettings.registerAppIdLPw(result.mPkgSetting, result.needsNewAppId());
+ return mPm.mSettings.registerAppIdLPw(installRequest.getScannedPackageSetting(),
+ installRequest.needsNewAppId());
}
}
return false;
@@ -3725,15 +3694,16 @@
/**
* Reverts any app ID creation that were made by
- * {@link #optimisticallyRegisterAppId(ScanResult)}. Note: this is only necessary if the
+ * {@link #optimisticallyRegisterAppId(InstallRequest)}. Note: this is only necessary if the
* referenced method returned true.
*/
- private void cleanUpAppIdCreation(@NonNull ScanResult result) {
+ private void cleanUpAppIdCreation(@NonNull InstallRequest installRequest) {
// iff we've acquired an app ID for a new package setting, remove it so that it can be
// acquired by another request.
- if (result.mPkgSetting.getAppId() > 0) {
+ if (installRequest.getScannedPackageSetting() != null
+ && installRequest.getScannedPackageSetting().getAppId() > 0) {
synchronized (mPm.mLock) {
- mPm.mSettings.removeAppIdLPw(result.mPkgSetting.getAppId());
+ mPm.mSettings.removeAppIdLPw(installRequest.getScannedPackageSetting().getAppId());
}
}
}
@@ -3857,7 +3827,6 @@
}
}
- @GuardedBy("mPm.mInstallLock")
private Pair<ScanResult, Boolean> scanSystemPackageLI(ParsedPackage parsedPackage,
@ParsingPackageUtils.ParseFlags int parseFlags,
@PackageManagerService.ScanFlags int scanFlags,
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 36bbf41..573082a 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -18,6 +18,7 @@
import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
+import static android.os.Process.INVALID_UID;
import static com.android.server.pm.PackageManagerService.TAG;
@@ -29,13 +30,19 @@
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.net.Uri;
+import android.os.Build;
+import android.os.Process;
import android.os.UserHandle;
import android.util.ExceptionUtils;
import android.util.Slog;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import java.io.File;
import java.util.ArrayList;
@@ -45,13 +52,59 @@
private final int mUserId;
@Nullable
private final InstallArgs mInstallArgs;
- @NonNull
- private final PackageInstalledInfo mInstalledInfo;
@Nullable
private Runnable mPostInstallRunnable;
@Nullable
private PackageRemovedInfo mRemovedInfo;
+ private @PackageManagerService.ScanFlags int mScanFlags;
+ private @ParsingPackageUtils.ParseFlags int mParseFlags;
+ private boolean mReplace;
+
+ @Nullable /* The original Package if it is being replaced, otherwise {@code null} */
+ private AndroidPackage mExistingPackage;
+ /** parsed package to be scanned */
+ @Nullable
+ private ParsedPackage mParsedPackage;
+ private boolean mClearCodeCache;
+ private boolean mSystem;
+ @Nullable
+ private PackageSetting mOriginalPs;
+ @Nullable
+ private PackageSetting mDisabledPs;
+
+ /** Package Installed Info */
+ @Nullable
+ private String mName;
+ private int mUid = -1;
+ // The set of users that originally had this package installed.
+ @Nullable
+ private int[] mOrigUsers;
+ // The set of users that now have this package installed.
+ @Nullable
+ private int[] mNewUsers;
+ @Nullable
+ private AndroidPackage mPkg;
+ private int mReturnCode;
+ @Nullable
+ private String mReturnMsg;
+ // The set of packages consuming this shared library or null if no consumers exist.
+ @Nullable
+ private ArrayList<AndroidPackage> mLibraryConsumers;
+ @Nullable
+ private PackageFreezer mFreezer;
+ /** The package this package replaces */
+ @Nullable
+ private String mOrigPackage;
+ @Nullable
+ private String mOrigPermission;
+ // The ApexInfo returned by ApexManager#installPackage, used by rebootless APEX install
+ @Nullable
+ private ApexInfo mApexInfo;
+
+ @Nullable
+ private ScanResult mScanResult;
+
// New install
InstallRequest(InstallingSession params) {
mUserId = params.getUser().getIdentifier();
@@ -63,7 +116,6 @@
params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
params.mDataLoaderType, params.mPackageSource);
- mInstalledInfo = new PackageInstalledInfo();
}
// Install existing package as user
@@ -71,56 +123,56 @@
Runnable runnable) {
mUserId = userId;
mInstallArgs = null;
- mInstalledInfo = new PackageInstalledInfo();
- mInstalledInfo.mReturnCode = returnCode;
- mInstalledInfo.mPkg = pkg;
- mInstalledInfo.mNewUsers = newUsers;
+ mReturnCode = returnCode;
+ mPkg = pkg;
+ mNewUsers = newUsers;
mPostInstallRunnable = runnable;
}
- private static class PackageInstalledInfo {
- String mName;
- int mUid = -1;
- // The set of users that originally had this package installed.
- int[] mOrigUsers;
- // The set of users that now have this package installed.
- int[] mNewUsers;
- AndroidPackage mPkg;
- int mReturnCode;
- String mReturnMsg;
- String mInstallerPackageName;
- // The set of packages consuming this shared library or null if no consumers exist.
- ArrayList<AndroidPackage> mLibraryConsumers;
- PackageFreezer mFreezer;
- // In some error cases we want to convey more info back to the observer
- String mOrigPackage;
- String mOrigPermission;
- // The ApexInfo returned by ApexManager#installPackage, used by rebootless APEX install
- ApexInfo mApexInfo;
+ // addForInit
+ InstallRequest(ParsedPackage parsedPackage, int parseFlags, int scanFlags,
+ @Nullable UserHandle user, ScanResult scanResult) {
+ if (user != null) {
+ mUserId = user.getIdentifier();
+ } else {
+ // APEX
+ mUserId = INVALID_UID;
+ }
+ mInstallArgs = null;
+ mParsedPackage = parsedPackage;
+ mParseFlags = parseFlags;
+ mScanFlags = scanFlags;
+ mScanResult = scanResult;
}
+ @Nullable
public String getName() {
- return mInstalledInfo.mName;
+ return mName;
}
+ @Nullable
public String getReturnMsg() {
- return mInstalledInfo.mReturnMsg;
+ return mReturnMsg;
}
+ @Nullable
public OriginInfo getOriginInfo() {
return mInstallArgs == null ? null : mInstallArgs.mOriginInfo;
}
+ @Nullable
public PackageRemovedInfo getRemovedInfo() {
return mRemovedInfo;
}
+ @Nullable
public String getOrigPackage() {
- return mInstalledInfo.mOrigPackage;
+ return mOrigPackage;
}
+ @Nullable
public String getOrigPermission() {
- return mInstalledInfo.mOrigPermission;
+ return mOrigPermission;
}
@Nullable
@@ -140,7 +192,7 @@
}
public int getReturnCode() {
- return mInstalledInfo.mReturnCode;
+ return mReturnCode;
}
@Nullable
@@ -160,13 +212,13 @@
@Nullable
public String getMovePackageName() {
- return (mInstallArgs != null && mInstallArgs.mMoveInfo != null)
+ return (mInstallArgs != null && mInstallArgs.mMoveInfo != null)
? mInstallArgs.mMoveInfo.mPackageName : null;
}
@Nullable
public String getMoveFromCodePath() {
- return (mInstallArgs != null && mInstallArgs.mMoveInfo != null)
+ return (mInstallArgs != null && mInstallArgs.mMoveInfo != null)
? mInstallArgs.mMoveInfo.mFromCodePath : null;
}
@@ -203,8 +255,9 @@
return mInstallArgs == null ? null : mInstallArgs.mVolumeUuid;
}
+ @Nullable
public AndroidPackage getPkg() {
- return mInstalledInfo.mPkg;
+ return mPkg;
}
@Nullable
@@ -256,13 +309,15 @@
@Nullable
public Uri getOriginUri() {
- return mInstallArgs == null ? null : Uri.fromFile(mInstallArgs.mOriginInfo.mResolvedFile);
+ return mInstallArgs == null ? null : Uri.fromFile(mInstallArgs.mOriginInfo.mResolvedFile);
}
+ @Nullable
public ApexInfo getApexInfo() {
- return mInstalledInfo.mApexInfo;
+ return mApexInfo;
}
+ @Nullable
public String getSourceInstallerPackageName() {
return mInstallArgs.mInstallSource.installerPackageName;
}
@@ -272,25 +327,33 @@
&& mInstallArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
}
+ @Nullable
public int[] getNewUsers() {
- return mInstalledInfo.mNewUsers;
+ return mNewUsers;
}
+ @Nullable
public int[] getOriginUsers() {
- return mInstalledInfo.mOrigUsers;
+ return mOrigUsers;
}
public int getUid() {
- return mInstalledInfo.mUid;
+ return mUid;
}
@Nullable
public String[] getInstallGrantPermissions() {
- return mInstallArgs == null ? null : mInstallArgs.mInstallGrantPermissions;
+ return mInstallArgs == null ? null : mInstallArgs.mInstallGrantPermissions;
}
+ @Nullable
public ArrayList<AndroidPackage> getLibraryConsumers() {
- return mInstalledInfo.mLibraryConsumers;
+ return mLibraryConsumers;
+ }
+
+ @Nullable
+ public AndroidPackage getExistingPackage() {
+ return mExistingPackage;
}
@Nullable
@@ -312,13 +375,170 @@
return mInstallArgs == null ? INSTALL_SCENARIO_DEFAULT : mInstallArgs.mInstallScenario;
}
+ @Nullable
+ public ParsedPackage getParsedPackage() {
+ return mParsedPackage;
+ }
+
+ public @ParsingPackageUtils.ParseFlags int getParseFlags() {
+ return mParseFlags;
+ }
+
+ public @PackageManagerService.ScanFlags int getScanFlags() {
+ return mScanFlags;
+ }
+
+ @Nullable
+ public String getExistingPackageName() {
+ if (mExistingPackage != null) {
+ return mExistingPackage.getPackageName();
+ }
+ return null;
+ }
+
+ @Nullable
+ public AndroidPackage getScanRequestOldPackage() {
+ assertScanResultExists();
+ return mScanResult.mRequest.mOldPkg;
+ }
+
+ public boolean isClearCodeCache() {
+ return mClearCodeCache;
+ }
+
+ public boolean isReplace() {
+ return mReplace;
+ }
+
+ public boolean isSystem() {
+ return mSystem;
+ }
+
+ @Nullable
+ public PackageSetting getOriginalPackageSetting() {
+ return mOriginalPs;
+ }
+
+ @Nullable
+ public PackageSetting getDisabledPackageSetting() {
+ return mDisabledPs;
+ }
+
+ @Nullable
+ public PackageSetting getScanRequestOldPackageSetting() {
+ assertScanResultExists();
+ return mScanResult.mRequest.mOldPkgSetting;
+ }
+
+ @Nullable
+ public PackageSetting getScanRequestOriginalPackageSetting() {
+ assertScanResultExists();
+ return mScanResult.mRequest.mOriginalPkgSetting;
+ }
+
+ @Nullable
+ public PackageSetting getScanRequestPackageSetting() {
+ assertScanResultExists();
+ return mScanResult.mRequest.mPkgSetting;
+ }
+
+ @Nullable
+ public String getRealPackageName() {
+ assertScanResultExists();
+ return mScanResult.mRequest.mRealPkgName;
+ }
+
+ @Nullable
+ public List<String> getChangedAbiCodePath() {
+ assertScanResultExists();
+ return mScanResult.mChangedAbiCodePath;
+ }
+
public boolean isForceQueryableOverride() {
return mInstallArgs != null && mInstallArgs.mForceQueryableOverride;
}
+ @Nullable
+ public SharedLibraryInfo getSdkSharedLibraryInfo() {
+ assertScanResultExists();
+ return mScanResult.mSdkSharedLibraryInfo;
+ }
+
+ @Nullable
+ public SharedLibraryInfo getStaticSharedLibraryInfo() {
+ assertScanResultExists();
+ return mScanResult.mStaticSharedLibraryInfo;
+ }
+
+ @Nullable
+ public List<SharedLibraryInfo> getDynamicSharedLibraryInfos() {
+ assertScanResultExists();
+ return mScanResult.mDynamicSharedLibraryInfos;
+ }
+
+ @Nullable
+ public PackageSetting getScannedPackageSetting() {
+ assertScanResultExists();
+ return mScanResult.mPkgSetting;
+ }
+
+ @Nullable
+ public PackageSetting getRealPackageSetting() {
+ // TODO: Fix this to have 1 mutable PackageSetting for scan/install. If the previous
+ // setting needs to be passed to have a comparison, hide it behind an immutable
+ // interface. There's no good reason to have 3 different ways to access the real
+ // PackageSetting object, only one of which is actually correct.
+ PackageSetting realPkgSetting = isExistingSettingCopied()
+ ? getScanRequestPackageSetting() : getScannedPackageSetting();
+ if (realPkgSetting == null) {
+ realPkgSetting = getScannedPackageSetting();
+ }
+ return realPkgSetting;
+ }
+
+ public boolean isExistingSettingCopied() {
+ assertScanResultExists();
+ return mScanResult.mExistingSettingCopied;
+ }
+
+ /**
+ * Whether the original PackageSetting needs to be updated with
+ * a new app ID. Useful when leaving a sharedUserId.
+ */
+ public boolean needsNewAppId() {
+ assertScanResultExists();
+ return mScanResult.mPreviousAppId != Process.INVALID_UID;
+ }
+
+ public int getPreviousAppId() {
+ assertScanResultExists();
+ return mScanResult.mPreviousAppId;
+ }
+
+ public boolean isPlatformPackage() {
+ assertScanResultExists();
+ return mScanResult.mRequest.mIsPlatformPackage;
+ }
+
+ public void assertScanResultExists() {
+ if (mScanResult == null) {
+ // Should not happen. This indicates a bug in the installation code flow
+ if (Build.IS_USERDEBUG || Build.IS_ENG) {
+ throw new IllegalStateException("ScanResult cannot be null.");
+ } else {
+ Slog.e(TAG, "ScanResult is null and it should not happen");
+ }
+ }
+
+ }
+
+ public void setScanFlags(int scanFlags) {
+ mScanFlags = scanFlags;
+ }
+
public void closeFreezer() {
- if (mInstalledInfo.mFreezer != null) {
- mInstalledInfo.mFreezer.close();
+ if (mFreezer != null) {
+ mFreezer.close();
}
}
@@ -341,57 +561,53 @@
}
public void setError(String msg, PackageManagerException e) {
- mInstalledInfo.mReturnCode = e.error;
+ mReturnCode = e.error;
setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e));
Slog.w(TAG, msg, e);
}
public void setReturnCode(int returnCode) {
- mInstalledInfo.mReturnCode = returnCode;
+ mReturnCode = returnCode;
}
public void setReturnMessage(String returnMsg) {
- mInstalledInfo.mReturnMsg = returnMsg;
+ mReturnMsg = returnMsg;
}
public void setApexInfo(ApexInfo apexInfo) {
- mInstalledInfo.mApexInfo = apexInfo;
+ mApexInfo = apexInfo;
}
public void setPkg(AndroidPackage pkg) {
- mInstalledInfo.mPkg = pkg;
+ mPkg = pkg;
}
public void setUid(int uid) {
- mInstalledInfo.mUid = uid;
+ mUid = uid;
}
public void setNewUsers(int[] newUsers) {
- mInstalledInfo.mNewUsers = newUsers;
+ mNewUsers = newUsers;
}
public void setOriginPackage(String originPackage) {
- mInstalledInfo.mOrigPackage = originPackage;
+ mOrigPackage = originPackage;
}
public void setOriginPermission(String originPermission) {
- mInstalledInfo.mOrigPermission = originPermission;
- }
-
- public void setInstallerPackageName(String installerPackageName) {
- mInstalledInfo.mInstallerPackageName = installerPackageName;
+ mOrigPermission = originPermission;
}
public void setName(String packageName) {
- mInstalledInfo.mName = packageName;
+ mName = packageName;
}
public void setOriginUsers(int[] userIds) {
- mInstalledInfo.mOrigUsers = userIds;
+ mOrigUsers = userIds;
}
public void setFreezer(PackageFreezer freezer) {
- mInstalledInfo.mFreezer = freezer;
+ mFreezer = freezer;
}
public void setRemovedInfo(PackageRemovedInfo removedInfo) {
@@ -399,6 +615,47 @@
}
public void setLibraryConsumers(ArrayList<AndroidPackage> libraryConsumers) {
- mInstalledInfo.mLibraryConsumers = libraryConsumers;
+ mLibraryConsumers = libraryConsumers;
+ }
+
+ public void setPrepareResult(boolean replace, int scanFlags,
+ int parseFlags, AndroidPackage existingPackage,
+ ParsedPackage packageToScan, boolean clearCodeCache, boolean system,
+ PackageSetting originalPs, PackageSetting disabledPs) {
+ mReplace = replace;
+ mScanFlags = scanFlags;
+ mParseFlags = parseFlags;
+ mExistingPackage = existingPackage;
+ mParsedPackage = packageToScan;
+ mClearCodeCache = clearCodeCache;
+ mSystem = system;
+ mOriginalPs = originalPs;
+ mDisabledPs = disabledPs;
+ }
+
+ public void setScanResult(@NonNull ScanResult scanResult) {
+ mScanResult = scanResult;
+ }
+
+ public void setScannedPackageSettingAppId(int appId) {
+ assertScanResultExists();
+ mScanResult.mPkgSetting.setAppId(appId);
+ }
+
+ public void setScannedPackageSettingFirstInstallTimeFromReplaced(
+ @Nullable PackageStateInternal replacedPkgSetting, int[] userId) {
+ assertScanResultExists();
+ mScanResult.mPkgSetting.setFirstInstallTimeFromReplaced(replacedPkgSetting, userId);
+ }
+
+ public void setScannedPackageSettingLastUpdateTime(long lastUpdateTim) {
+ assertScanResultExists();
+ mScanResult.mPkgSetting.setLastUpdateTime(lastUpdateTim);
+ }
+
+ public void setRemovedAppId(int appId) {
+ if (mRemovedInfo != null) {
+ mRemovedInfo.mRemovedAppId = appId;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 8d5a5e1..16b3a81 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -42,7 +42,7 @@
import android.os.Environment;
import android.os.Trace;
import android.os.UserHandle;
-import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
@@ -60,7 +60,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
+import java.util.Set;
class InstallingSession {
final OriginInfo mOriginInfo;
@@ -599,7 +599,7 @@
*/
private class MultiPackageInstallingSession {
private final List<InstallingSession> mChildInstallingSessions;
- private final Map<InstallRequest, Integer> mCurrentState;
+ private final Set<InstallRequest> mCurrentInstallRequests;
@NonNull
final PackageManagerService mPm;
final UserHandle mUser;
@@ -618,7 +618,7 @@
final InstallingSession childInstallingSession = childInstallingSessions.get(i);
childInstallingSession.mParentInstallingSession = this;
}
- this.mCurrentState = new ArrayMap<>(mChildInstallingSessions.size());
+ mCurrentInstallRequests = new ArraySet<>(mChildInstallingSessions.size());
}
public void start() {
@@ -636,23 +636,24 @@
}
public void tryProcessInstallRequest(InstallRequest request) {
- mCurrentState.put(request, request.getReturnCode());
- if (mCurrentState.size() != mChildInstallingSessions.size()) {
+ mCurrentInstallRequests.add(request);
+ if (mCurrentInstallRequests.size() != mChildInstallingSessions.size()) {
return;
}
int completeStatus = PackageManager.INSTALL_SUCCEEDED;
- for (Integer status : mCurrentState.values()) {
- if (status == PackageManager.INSTALL_UNKNOWN) {
+ for (InstallRequest installRequest : mCurrentInstallRequests) {
+ if (installRequest.getReturnCode() == PackageManager.INSTALL_UNKNOWN) {
return;
- } else if (status != PackageManager.INSTALL_SUCCEEDED) {
- completeStatus = status;
+ } else if (installRequest.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
+ completeStatus = installRequest.getReturnCode();
break;
}
}
- final List<InstallRequest> installRequests = new ArrayList<>(mCurrentState.size());
- for (Map.Entry<InstallRequest, Integer> entry : mCurrentState.entrySet()) {
- entry.getKey().setReturnCode(completeStatus);
- installRequests.add(entry.getKey());
+ final List<InstallRequest> installRequests = new ArrayList<>(
+ mCurrentInstallRequests.size());
+ for (InstallRequest installRequest : mCurrentInstallRequests) {
+ installRequest.setReturnCode(completeStatus);
+ installRequests.add(installRequest);
}
int finalCompleteStatus = completeStatus;
mPm.mHandler.post(() -> processInstallRequests(
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index bedc12a..032d030 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -46,8 +46,6 @@
import android.util.PackageUtils;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -55,6 +53,8 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 7774b6a..f1453c8 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -27,9 +27,9 @@
import android.util.Base64;
import android.util.LongSparseArray;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.SharedUserApi;
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index 8534fab..84324f2 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -3,8 +3,6 @@
jsharkey@android.com
jsharkey@google.com
narayan@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
include /PACKAGE_MANAGER_OWNERS
# apex support
@@ -26,16 +24,10 @@
per-file PackageUsage.java = file:dex/OWNERS
# multi user / cross profile
-per-file CrossProfileAppsServiceImpl.java = omakoto@google.com, yamasani@google.com
-per-file CrossProfileAppsService.java = omakoto@google.com, yamasani@google.com
-per-file CrossProfileIntentFilter.java = omakoto@google.com, yamasani@google.com
-per-file CrossProfileIntentResolver.java = omakoto@google.com, yamasani@google.com
+per-file CrossProfile* = file:MULTIUSER_AND_ENTERPRISE_OWNERS
per-file RestrictionsSet.java = file:MULTIUSER_AND_ENTERPRISE_OWNERS
-per-file UserManager* = file:/MULTIUSER_OWNERS
per-file UserRestriction* = file:MULTIUSER_AND_ENTERPRISE_OWNERS
-per-file UserSystemPackageInstaller* = file:/MULTIUSER_OWNERS
-per-file UserTypeDetails.java = file:/MULTIUSER_OWNERS
-per-file UserTypeFactory.java = file:/MULTIUSER_OWNERS
+per-file User* = file:/MULTIUSER_OWNERS
# security
per-file KeySetHandle.java = cbrubaker@google.com, nnk@google.com
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 218d9d1..cc1d879 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -78,8 +78,6 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.R;
@@ -89,6 +87,8 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.ImageUtils;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 022bf3c..5df73a6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -29,6 +29,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
+import static android.content.pm.PackageManager.INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -139,8 +140,6 @@
import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.R;
@@ -156,6 +155,8 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.dex.DexManager;
@@ -4243,6 +4244,13 @@
public void requestUserPreapproval(@NonNull PreapprovalDetails details,
@NonNull IntentSender statusReceiver) {
validatePreapprovalRequest(details, statusReceiver);
+
+ if (!mPm.isPreapprovalRequestAvailable()) {
+ sendUpdateToRemoteStatusReceiver(INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE,
+ "Request user pre-approval is currently not available.", null /* extras */);
+ return;
+ }
+
dispatchPreapprovalRequest();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6e54d0b..dfbe68a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -161,8 +161,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.Display;
@@ -181,6 +179,8 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.permission.persistence.RuntimePermissionsPersistence;
import com.android.server.EventLogTags;
import com.android.server.FgThread;
@@ -495,6 +495,15 @@
private static final String PROPERTY_KNOWN_DIGESTERS_LIST = "known_digesters_list";
/**
+ * Whether of not requesting the approval before committing sessions is available.
+ *
+ * Flag type: {@code boolean}
+ * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+ */
+ private static final String PROPERTY_IS_PRE_APPROVAL_REQUEST_AVAILABLE =
+ "is_preapproval_available";
+
+ /**
* The default response for package verification timeout.
*
* This can be either PackageManager.VERIFICATION_ALLOW or
@@ -6894,6 +6903,16 @@
}
}
+ static boolean isPreapprovalRequestAvailable() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getBoolean(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ PROPERTY_IS_PRE_APPROVAL_REQUEST_AVAILABLE, true /* defaultValue */);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
/**
* Returns the array containing per-uid timeout configuration.
* This is derived from DeviceConfig flags.
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 76858d9..3c1cba3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -316,6 +316,8 @@
return runCreateUser();
case "remove-user":
return runRemoveUser();
+ case "rename-user":
+ return runRenameUser();
case "set-user-restriction":
return runSetUserRestriction();
case "supports-multiple-users":
@@ -3024,6 +3026,28 @@
}
}
+ private int runRenameUser() throws RemoteException {
+ String arg = getNextArg();
+ if (arg == null) {
+ getErrPrintWriter().println("Error: no user id specified.");
+ return 1;
+ }
+ int userId = resolveUserId(UserHandle.parseUserArg(arg));
+
+ String name = getNextArg();
+ if (name == null) {
+ Slog.i(TAG, "Resetting name of user " + userId);
+ } else {
+ Slog.i(TAG, "Renaming user " + userId + " to '" + name + "'");
+ }
+
+ IUserManager um = IUserManager.Stub.asInterface(
+ ServiceManager.getService(Context.USER_SERVICE));
+ um.setUserName(userId, name);
+
+ return 0;
+ }
+
public int runSetUserRestriction() throws RemoteException {
int userId = UserHandle.USER_SYSTEM;
String opt = getNextOption();
@@ -3937,6 +3961,11 @@
return res;
}
+ // Resolves the userId; supports UserHandle.USER_CURRENT, but not other special values
+ private @UserIdInt int resolveUserId(@UserIdInt int userId) {
+ return userId == UserHandle.USER_CURRENT ? ActivityManager.getCurrentUser() : userId;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -4208,6 +4237,9 @@
pw.println(" switch or reboot)");
pw.println(" --wait: Wait until user is removed. Ignored if set-ephemeral-if-in-use");
pw.println("");
+ pw.println(" rename-user USER_ID [USER_NAME]");
+ pw.println(" Rename USER_ID with USER_NAME (or null when [USER_NAME] is not set)");
+ pw.println("");
pw.println(" set-user-restriction [--user USER_ID] RESTRICTION VALUE");
pw.println("");
pw.println(" get-max-users");
diff --git a/services/core/java/com/android/server/pm/PackageSignatures.java b/services/core/java/com/android/server/pm/PackageSignatures.java
index 76364fe..90f57a7 100644
--- a/services/core/java/com/android/server/pm/PackageSignatures.java
+++ b/services/core/java/com/android/server/pm/PackageSignatures.java
@@ -21,10 +21,10 @@
import android.content.pm.SigningDetails;
import android.content.pm.SigningDetails.SignatureSchemeVersion;
import android.util.Log;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/PersistentPreferredActivity.java b/services/core/java/com/android/server/pm/PersistentPreferredActivity.java
index ad3950c..d0ee0c8 100644
--- a/services/core/java/com/android/server/pm/PersistentPreferredActivity.java
+++ b/services/core/java/com/android/server/pm/PersistentPreferredActivity.java
@@ -19,10 +19,10 @@
import android.content.ComponentName;
import android.content.IntentFilter;
import android.util.Log;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.utils.SnapshotCache;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/pm/PreferredActivity.java b/services/core/java/com/android/server/pm/PreferredActivity.java
index 5bc915f..1a49bf9 100644
--- a/services/core/java/com/android/server/pm/PreferredActivity.java
+++ b/services/core/java/com/android/server/pm/PreferredActivity.java
@@ -19,10 +19,10 @@
import android.content.ComponentName;
import android.content.IntentFilter;
import android.util.Log;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.utils.SnapshotCache;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index 0ca5febd..e7727f0 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -42,11 +42,11 @@
import android.util.PrintStreamPrinter;
import android.util.Slog;
import android.util.SparseBooleanArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.util.ArrayUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.pkg.PackageStateInternal;
diff --git a/services/core/java/com/android/server/pm/PreferredComponent.java b/services/core/java/com/android/server/pm/PreferredComponent.java
index 2a1ca2c..5507e7c 100644
--- a/services/core/java/com/android/server/pm/PreferredComponent.java
+++ b/services/core/java/com/android/server/pm/PreferredComponent.java
@@ -23,10 +23,10 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.pm.pkg.PackageUserState;
diff --git a/services/core/java/com/android/server/pm/PrepareResult.java b/services/core/java/com/android/server/pm/PrepareResult.java
deleted file mode 100644
index e074f44a..0000000
--- a/services/core/java/com/android/server/pm/PrepareResult.java
+++ /dev/null
@@ -1,54 +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.server.pm;
-
-import android.annotation.Nullable;
-
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.AndroidPackage;
-
-/**
- * The set of data needed to successfully install the prepared package. This includes data that
- * will be used to scan and reconcile the package.
- */
-final class PrepareResult {
- public final boolean mReplace;
- public final int mScanFlags;
- public final int mParseFlags;
- @Nullable /* The original Package if it is being replaced, otherwise {@code null} */
- public final AndroidPackage mExistingPackage;
- public final ParsedPackage mPackageToScan;
- public final boolean mClearCodeCache;
- public final boolean mSystem;
- public final PackageSetting mOriginalPs;
- public final PackageSetting mDisabledPs;
-
- PrepareResult(boolean replace, int scanFlags,
- int parseFlags, AndroidPackage existingPackage,
- ParsedPackage packageToScan, boolean clearCodeCache, boolean system,
- PackageSetting originalPs, PackageSetting disabledPs) {
- mReplace = replace;
- mScanFlags = scanFlags;
- mParseFlags = parseFlags;
- mExistingPackage = existingPackage;
- mPackageToScan = packageToScan;
- mClearCodeCache = clearCodeCache;
- mSystem = system;
- mOriginalPs = originalPs;
- mDisabledPs = disabledPs;
- }
-}
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 165b450..ffce69e 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -38,34 +38,44 @@
import java.util.List;
import java.util.Map;
+/**
+ * Package scan results and related request details used to reconcile the potential addition of
+ * one or more packages to the system.
+ *
+ * Reconcile will take a set of package details that need to be committed to the system and make
+ * sure that they are valid in the context of the system and the other installing apps. Any
+ * invalid state or app will result in a failed reconciliation and thus whatever operation (such
+ * as install) led to the request.
+ */
final class ReconcilePackageUtils {
public static Map<String, ReconciledPackage> reconcilePackages(
- final ReconcileRequest request, SharedLibrariesImpl sharedLibraries,
+ List<InstallRequest> installRequests,
+ Map<String, AndroidPackage> allPackages,
+ Map<String, Settings.VersionInfo> versionInfos,
+ SharedLibrariesImpl sharedLibraries,
KeySetManagerService ksms, Settings settings)
throws ReconcileFailure {
- final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
-
- final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.size());
+ final Map<String, ReconciledPackage> result = new ArrayMap<>(installRequests.size());
// make a copy of the existing set of packages so we can combine them with incoming packages
final ArrayMap<String, AndroidPackage> combinedPackages =
- new ArrayMap<>(request.mAllPackages.size() + scannedPackages.size());
+ new ArrayMap<>(allPackages.size() + installRequests.size());
- combinedPackages.putAll(request.mAllPackages);
+ combinedPackages.putAll(allPackages);
final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
new ArrayMap<>();
- for (String installPackageName : scannedPackages.keySet()) {
- final ScanResult scanResult = scannedPackages.get(installPackageName);
+ for (InstallRequest installRequest : installRequests) {
+ final String installPackageName = installRequest.getParsedPackage().getPackageName();
// add / replace existing with incoming packages
- combinedPackages.put(scanResult.mPkgSetting.getPackageName(),
- scanResult.mRequest.mParsedPackage);
+ combinedPackages.put(installRequest.getScannedPackageSetting().getPackageName(),
+ installRequest.getParsedPackage());
// in the first pass, we'll build up the set of incoming shared libraries
final List<SharedLibraryInfo> allowedSharedLibInfos =
- sharedLibraries.getAllowedSharedLibInfos(scanResult);
+ sharedLibraries.getAllowedSharedLibInfos(installRequest);
if (allowedSharedLibInfos != null) {
for (SharedLibraryInfo info : allowedSharedLibInfos) {
if (!SharedLibraryUtils.addSharedLibraryToPackageVersionMap(
@@ -76,24 +86,18 @@
}
}
- // the following may be null if we're just reconciling on boot (and not during install)
- final InstallRequest installRequest = request.mInstallRequests.get(installPackageName);
- final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName);
- final boolean isInstall = installRequest != null;
- if (isInstall && prepareResult == null) {
- throw new ReconcileFailure("Reconcile arguments are not balanced for "
- + installPackageName + "!");
- }
+
final DeletePackageAction deletePackageAction;
// we only want to try to delete for non system apps
- if (isInstall && prepareResult.mReplace && !prepareResult.mSystem) {
- final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0;
+ if (installRequest.isReplace() && !installRequest.isSystem()) {
+ final boolean killApp = (installRequest.getScanFlags() & SCAN_DONT_KILL_APP) == 0;
final int deleteFlags = PackageManager.DELETE_KEEP_DATA
| (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(
installRequest.getRemovedInfo(),
- prepareResult.mOriginalPs, prepareResult.mDisabledPs,
+ installRequest.getOriginalPackageSetting(),
+ installRequest.getDisabledPackageSetting(),
deleteFlags, null /* all users */);
if (deletePackageAction == null) {
throw new ReconcileFailure(
@@ -104,21 +108,24 @@
deletePackageAction = null;
}
- final int scanFlags = scanResult.mRequest.mScanFlags;
- final int parseFlags = scanResult.mRequest.mParseFlags;
- final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
-
- final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting;
+ final int scanFlags = installRequest.getScanFlags();
+ final int parseFlags = installRequest.getParseFlags();
+ final ParsedPackage parsedPackage = installRequest.getParsedPackage();
+ final PackageSetting disabledPkgSetting = installRequest.getDisabledPackageSetting();
final PackageSetting lastStaticSharedLibSetting =
- scanResult.mStaticSharedLibraryInfo == null ? null
- : sharedLibraries.getStaticSharedLibLatestVersionSetting(scanResult);
+ installRequest.getStaticSharedLibraryInfo() == null ? null
+ : sharedLibraries.getStaticSharedLibLatestVersionSetting(
+ installRequest);
final PackageSetting signatureCheckPs =
- (prepareResult != null && lastStaticSharedLibSetting != null)
+ lastStaticSharedLibSetting != null
? lastStaticSharedLibSetting
- : scanResult.mPkgSetting;
+ : installRequest.getScannedPackageSetting();
boolean removeAppKeySetData = false;
boolean sharedUserSignaturesChanged = false;
SigningDetails signingDetails = null;
+ if (parsedPackage != null) {
+ signingDetails = parsedPackage.getSigningDetails();
+ }
SharedUserSetting sharedUserSetting = settings.getSharedUserSettingLPr(
signatureCheckPs);
if (ksms.shouldCheckUpgradeKeySetLocked(
@@ -138,28 +145,21 @@
PackageManagerService.reportSettingsProblem(Log.WARN, msg);
}
}
- signingDetails = parsedPackage.getSigningDetails();
} else {
-
try {
- final Settings.VersionInfo versionInfo =
- request.mVersionInfos.get(installPackageName);
+ final Settings.VersionInfo versionInfo = versionInfos.get(installPackageName);
final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
- final boolean isRollback = installRequest != null
- && installRequest.isRollback();
+ final boolean isRollback = installRequest.isRollback();
final boolean compatMatch =
PackageManagerServiceUtils.verifySignatures(signatureCheckPs,
sharedUserSetting, disabledPkgSetting,
- parsedPackage.getSigningDetails(), compareCompat,
+ signingDetails, compareCompat,
compareRecover, isRollback);
// The new KeySets will be re-added later in the scanning process.
if (compatMatch) {
removeAppKeySetData = true;
}
- // We just determined the app is signed correctly, so bring
- // over the latest parsed certs.
- signingDetails = parsedPackage.getSigningDetails();
// if this is is a sharedUser, check to see if the new package is signed by a
// newer
@@ -257,13 +257,12 @@
}
result.put(installPackageName,
- new ReconciledPackage(request, installRequest, scanResult.mPkgSetting,
- request.mPreparedPackages.get(installPackageName), scanResult,
+ new ReconciledPackage(installRequests, allPackages, installRequest,
deletePackageAction, allowedSharedLibInfos, signingDetails,
sharedUserSignaturesChanged, removeAppKeySetData));
}
- for (String installPackageName : scannedPackages.keySet()) {
+ for (InstallRequest installRequest : installRequests) {
// Check all shared libraries and map to their actual file path.
// We only do this here for apps not on a system dir, because those
// are the only ones that can fail an install due to this. We
@@ -271,16 +270,16 @@
// library paths after the scan is done. Also during the initial
// scan don't update any libs as we do this wholesale after all
// apps are scanned to avoid dependency based scanning.
- final ScanResult scanResult = scannedPackages.get(installPackageName);
- if ((scanResult.mRequest.mScanFlags & SCAN_BOOTING) != 0
- || (scanResult.mRequest.mParseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
+ if ((installRequest.getScanFlags() & SCAN_BOOTING) != 0
+ || (installRequest.getParseFlags() & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
!= 0) {
continue;
}
+ final String installPackageName = installRequest.getParsedPackage().getPackageName();
try {
result.get(installPackageName).mCollectedSharedLibraryInfos =
sharedLibraries.collectSharedLibraryInfos(
- scanResult.mRequest.mParsedPackage, combinedPackages,
+ installRequest.getParsedPackage(), combinedPackages,
incomingSharedLibraries);
} catch (PackageManagerException e) {
throw new ReconcileFailure(e.error, e.getMessage());
diff --git a/services/core/java/com/android/server/pm/ReconcileRequest.java b/services/core/java/com/android/server/pm/ReconcileRequest.java
deleted file mode 100644
index 3568c15..0000000
--- a/services/core/java/com/android/server/pm/ReconcileRequest.java
+++ /dev/null
@@ -1,59 +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.server.pm;
-
-import com.android.server.pm.pkg.AndroidPackage;
-
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * Package scan results and related request details used to reconcile the potential addition of
- * one or more packages to the system.
- *
- * Reconcile will take a set of package details that need to be committed to the system and make
- * sure that they are valid in the context of the system and the other installing apps. Any
- * invalid state or app will result in a failed reconciliation and thus whatever operation (such
- * as install) led to the request.
- */
-final class ReconcileRequest {
- public final Map<String, ScanResult> mScannedPackages;
-
- public final Map<String, AndroidPackage> mAllPackages;
- public final Map<String, InstallRequest> mInstallRequests;
- public final Map<String, PrepareResult> mPreparedPackages;
- public final Map<String, Settings.VersionInfo> mVersionInfos;
-
- ReconcileRequest(Map<String, ScanResult> scannedPackages,
- Map<String, InstallRequest> installRequests,
- Map<String, PrepareResult> preparedPackages,
- Map<String, AndroidPackage> allPackages,
- Map<String, Settings.VersionInfo> versionInfos) {
- mScannedPackages = scannedPackages;
- mInstallRequests = installRequests;
- mPreparedPackages = preparedPackages;
- mAllPackages = allPackages;
- mVersionInfos = versionInfos;
- }
-
- ReconcileRequest(Map<String, ScanResult> scannedPackages,
- Map<String, AndroidPackage> allPackages,
- Map<String, Settings.VersionInfo> versionInfos) {
- this(scannedPackages, Collections.emptyMap(),
- Collections.emptyMap(), allPackages, versionInfos);
- }
-}
diff --git a/services/core/java/com/android/server/pm/ReconciledPackage.java b/services/core/java/com/android/server/pm/ReconciledPackage.java
index d4da6c7..701baee 100644
--- a/services/core/java/com/android/server/pm/ReconciledPackage.java
+++ b/services/core/java/com/android/server/pm/ReconciledPackage.java
@@ -17,7 +17,6 @@
package com.android.server.pm;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.util.ArrayMap;
@@ -33,12 +32,9 @@
* TODO: move most of the data contained here into a PackageSetting for commit.
*/
final class ReconciledPackage {
- public final ReconcileRequest mRequest;
- public final PackageSetting mPkgSetting;
- public final ScanResult mScanResult;
- // TODO: Remove install-specific details from the reconcile result
- @Nullable public final PrepareResult mPrepareResult;
- @Nullable public final InstallRequest mInstallRequest;
+ private final List<InstallRequest> mInstallRequests;
+ private final Map<String, AndroidPackage> mAllPackages;
+ @NonNull public final InstallRequest mInstallRequest;
public final DeletePackageAction mDeletePackageAction;
public final List<SharedLibraryInfo> mAllowedSharedLibraryInfos;
public final SigningDetails mSigningDetails;
@@ -46,21 +42,17 @@
public ArrayList<SharedLibraryInfo> mCollectedSharedLibraryInfos;
public final boolean mRemoveAppKeySetData;
- ReconciledPackage(ReconcileRequest request,
+ ReconciledPackage(List<InstallRequest> installRequests,
+ Map<String, AndroidPackage> allPackages,
InstallRequest installRequest,
- PackageSetting pkgSetting,
- PrepareResult prepareResult,
- ScanResult scanResult,
DeletePackageAction deletePackageAction,
List<SharedLibraryInfo> allowedSharedLibraryInfos,
SigningDetails signingDetails,
boolean sharedUserSignaturesChanged,
boolean removeAppKeySetData) {
- mRequest = request;
+ mInstallRequests = installRequests;
+ mAllPackages = allPackages;
mInstallRequest = installRequest;
- mPkgSetting = pkgSetting;
- mPrepareResult = prepareResult;
- mScanResult = scanResult;
mDeletePackageAction = deletePackageAction;
mAllowedSharedLibraryInfos = allowedSharedLibraryInfos;
mSigningDetails = signingDetails;
@@ -75,13 +67,13 @@
*/
@NonNull Map<String, AndroidPackage> getCombinedAvailablePackages() {
final ArrayMap<String, AndroidPackage> combined =
- new ArrayMap<>(mRequest.mAllPackages.size() + mRequest.mScannedPackages.size());
+ new ArrayMap<>(mAllPackages.size() + mInstallRequests.size());
- combined.putAll(mRequest.mAllPackages);
+ combined.putAll(mAllPackages);
- for (ScanResult scanResult : mRequest.mScannedPackages.values()) {
- combined.put(scanResult.mPkgSetting.getPackageName(),
- scanResult.mRequest.mParsedPackage);
+ for (InstallRequest installRequest : mInstallRequests) {
+ combined.put(installRequest.getScannedPackageSetting().getPackageName(),
+ installRequest.getParsedPackage());
}
return combined;
diff --git a/services/core/java/com/android/server/pm/RestrictionsSet.java b/services/core/java/com/android/server/pm/RestrictionsSet.java
index e5a70c3..e7ad5b9 100644
--- a/services/core/java/com/android/server/pm/RestrictionsSet.java
+++ b/services/core/java/com/android/server/pm/RestrictionsSet.java
@@ -22,10 +22,10 @@
import android.os.Bundle;
import android.os.UserManager;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.BundleUtils;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index bd3c7dd..a905df9 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -462,7 +462,7 @@
}
}
- return new ScanResult(request, true, pkgSetting, changedAbiCodePath,
+ return new ScanResult(request, pkgSetting, changedAbiCodePath,
!createNewPackage /* existingSettingCopied */,
Process.INVALID_UID /* previousAppId */ , sdkLibraryInfo,
staticSharedLibraryInfo, dynamicSharedLibraryInfos);
diff --git a/services/core/java/com/android/server/pm/ScanResult.java b/services/core/java/com/android/server/pm/ScanResult.java
index e2860ca..750e893 100644
--- a/services/core/java/com/android/server/pm/ScanResult.java
+++ b/services/core/java/com/android/server/pm/ScanResult.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.SharedLibraryInfo;
import android.os.Process;
@@ -28,9 +29,7 @@
@VisibleForTesting
final class ScanResult {
/** The request that initiated the scan that produced this result. */
- public final ScanRequest mRequest;
- /** Whether or not the package scan was successful */
- public final boolean mSuccess;
+ @NonNull public final ScanRequest mRequest;
/**
* Whether or not the original PackageSetting needs to be updated with this result on
* commit.
@@ -58,7 +57,7 @@
public final List<SharedLibraryInfo> mDynamicSharedLibraryInfos;
ScanResult(
- ScanRequest request, boolean success,
+ @NonNull ScanRequest request,
@Nullable PackageSetting pkgSetting,
@Nullable List<String> changedAbiCodePath, boolean existingSettingCopied,
int previousAppId,
@@ -66,7 +65,6 @@
SharedLibraryInfo staticSharedLibraryInfo,
List<SharedLibraryInfo> dynamicSharedLibraryInfos) {
mRequest = request;
- mSuccess = success;
mPkgSetting = pkgSetting;
mChangedAbiCodePath = changedAbiCodePath;
mExistingSettingCopied = existingSettingCopied;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f2a7651..12c9d4b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -83,8 +83,6 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
@@ -96,6 +94,8 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.permission.persistence.RuntimePermissionsPersistence;
import com.android.permission.persistence.RuntimePermissionsState;
import com.android.server.LocalServices;
@@ -4185,15 +4185,16 @@
// such as APEX
continue;
}
- // Need to create a data directory for all apps installed for this user.
- // Accumulate all required args and call the installer after mPackages lock
- // has been released
+ // We need to create the DE data directory for all apps installed for this user.
+ // (CE storage is not ready yet; the CE data directories will be created later,
+ // when the user is "unlocked".) Accumulate all required args, and call the
+ // installer after the mPackages lock has been released.
final String seInfo = AndroidPackageUtils.getSeInfo(ps.getPkg(), ps);
final boolean usesSdk = !ps.getPkg().getUsesSdkLibraries().isEmpty();
final CreateAppDataArgs args = Installer.buildCreateAppDataArgs(
ps.getVolumeUuid(), ps.getPackageName(), userHandle,
- StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE,
- ps.getAppId(), seInfo, ps.getPkg().getTargetSdkVersion(), usesSdk);
+ StorageManager.FLAG_STORAGE_DE, ps.getAppId(), seInfo,
+ ps.getPkg().getTargetSdkVersion(), usesSdk);
batch.createAppData(args);
} else {
// Make sure the app is excluded from storage mapping for this user
diff --git a/services/core/java/com/android/server/pm/SettingsXml.java b/services/core/java/com/android/server/pm/SettingsXml.java
index c53fef7..5fb6731 100644
--- a/services/core/java/com/android/server/pm/SettingsXml.java
+++ b/services/core/java/com/android/server/pm/SettingsXml.java
@@ -19,10 +19,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/ShareTargetInfo.java b/services/core/java/com/android/server/pm/ShareTargetInfo.java
index 660874e..bfb5f39 100644
--- a/services/core/java/com/android/server/pm/ShareTargetInfo.java
+++ b/services/core/java/com/android/server/pm/ShareTargetInfo.java
@@ -17,8 +17,9 @@
import android.annotation.NonNull;
import android.text.TextUtils;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 094e748..aa23d8d 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -422,15 +422,18 @@
* Given a package scanned result of a static shared library, returns its package setting of
* the latest version
*
- * @param scanResult The scanned result of a static shared library package.
+ * @param installRequest The install result of a static shared library package.
* @return The package setting that represents the latest version of shared library info.
*/
@Nullable
- PackageSetting getStaticSharedLibLatestVersionSetting(@NonNull ScanResult scanResult) {
+ PackageSetting getStaticSharedLibLatestVersionSetting(@NonNull InstallRequest installRequest) {
+ if (installRequest.getParsedPackage() == null) {
+ return null;
+ }
PackageSetting sharedLibPackage = null;
synchronized (mPm.mLock) {
final SharedLibraryInfo latestSharedLibraVersionLPr =
- getLatestStaticSharedLibraVersionLPr(scanResult.mRequest.mParsedPackage);
+ getLatestStaticSharedLibraVersionLPr(installRequest.getParsedPackage());
if (latestSharedLibraVersionLPr != null) {
sharedLibPackage = mPm.mSettings.getPackageLPr(
latestSharedLibraVersionLPr.getPackageName());
@@ -823,34 +826,35 @@
* Compare the newly scanned package with current system state to see which of its declared
* shared libraries should be allowed to be added to the system.
*/
- List<SharedLibraryInfo> getAllowedSharedLibInfos(ScanResult scanResult) {
+ List<SharedLibraryInfo> getAllowedSharedLibInfos(InstallRequest installRequest) {
// Let's used the parsed package as scanResult.pkgSetting may be null
- final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
- if (scanResult.mSdkSharedLibraryInfo == null && scanResult.mStaticSharedLibraryInfo == null
- && scanResult.mDynamicSharedLibraryInfos == null) {
+ final ParsedPackage parsedPackage = installRequest.getParsedPackage();
+ if (installRequest.getSdkSharedLibraryInfo() == null
+ && installRequest.getStaticSharedLibraryInfo() == null
+ && installRequest.getDynamicSharedLibraryInfos() == null) {
return null;
}
// Any app can add new SDKs and static shared libraries.
- if (scanResult.mSdkSharedLibraryInfo != null) {
- return Collections.singletonList(scanResult.mSdkSharedLibraryInfo);
+ if (installRequest.getSdkSharedLibraryInfo() != null) {
+ return Collections.singletonList(installRequest.getSdkSharedLibraryInfo());
}
- if (scanResult.mStaticSharedLibraryInfo != null) {
- return Collections.singletonList(scanResult.mStaticSharedLibraryInfo);
+ if (installRequest.getStaticSharedLibraryInfo() != null) {
+ return Collections.singletonList(installRequest.getStaticSharedLibraryInfo());
}
- final boolean hasDynamicLibraries = parsedPackage.isSystem()
- && scanResult.mDynamicSharedLibraryInfos != null;
+ final boolean hasDynamicLibraries = parsedPackage != null && parsedPackage.isSystem()
+ && installRequest.getDynamicSharedLibraryInfos() != null;
if (!hasDynamicLibraries) {
return null;
}
- final boolean isUpdatedSystemApp = scanResult.mPkgSetting.getPkgState()
- .isUpdatedSystemApp();
+ final boolean isUpdatedSystemApp = installRequest.getScannedPackageSetting() != null
+ && installRequest.getScannedPackageSetting().getPkgState().isUpdatedSystemApp();
// We may not yet have disabled the updated package yet, so be sure to grab the
// current setting if that's the case.
final PackageSetting updatedSystemPs = isUpdatedSystemApp
- ? scanResult.mRequest.mDisabledPkgSetting == null
- ? scanResult.mRequest.mOldPkgSetting
- : scanResult.mRequest.mDisabledPkgSetting
+ ? installRequest.getDisabledPackageSetting() == null
+ ? installRequest.getScanRequestOldPackageSetting()
+ : installRequest.getDisabledPackageSetting()
: null;
if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null
|| updatedSystemPs.getPkg().getLibraryNames() == null)) {
@@ -859,8 +863,8 @@
return null;
}
final ArrayList<SharedLibraryInfo> infos =
- new ArrayList<>(scanResult.mDynamicSharedLibraryInfos.size());
- for (SharedLibraryInfo info : scanResult.mDynamicSharedLibraryInfos) {
+ new ArrayList<>(installRequest.getDynamicSharedLibraryInfos().size());
+ for (SharedLibraryInfo info : installRequest.getDynamicSharedLibraryInfos()) {
final String name = info.getName();
if (isUpdatedSystemApp) {
// New library entries can only be added through the
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index c6a7dd7..0d99075 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -24,11 +24,11 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.ShortcutService.DumpFilter;
import com.android.server.pm.ShortcutUser.PackageWithUser;
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 890c891..0362ddd 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -53,8 +53,6 @@
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -65,6 +63,8 @@
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.ShortcutService.DumpFilter;
import com.android.server.pm.ShortcutService.ShortcutOperation;
import com.android.server.pm.ShortcutService.Stats;
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index fce6610..79b725d 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -23,10 +23,10 @@
import android.content.pm.Signature;
import android.content.pm.SigningInfo;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.backup.BackupUtils;
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 7800183..e20330d 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -22,11 +22,11 @@
import android.graphics.Bitmap;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlSerializer;
import org.json.JSONException;
import org.json.JSONObject;
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 0b20683..b6f09ff 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -100,8 +100,6 @@
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TypedValue;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.IWindowManager;
@@ -116,6 +114,8 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.util.StatLogger;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.ShortcutUser.PackageWithUser;
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index b9fd2fd..20bbf46 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -30,14 +30,14 @@
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.FgThread;
import com.android.server.pm.ShortcutService.DumpFilter;
import com.android.server.pm.ShortcutService.InvalidFileFormatException;
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 477e260..aeb11b7 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -158,7 +158,7 @@
final AndroidPackage pkg;
try {
pkg = installPackageHelper.scanSystemPackageTracedLI(
- ps.getPath(), parseFlags, SCAN_INITIAL, null);
+ ps.getPath(), parseFlags, SCAN_INITIAL);
loaded.add(pkg);
} catch (PackageManagerException e) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f578fb5..2bfee8c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -103,8 +103,6 @@
import android.util.StatsEvent;
import android.util.TimeUtils;
import android.util.TypedValue;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.Display;
@@ -119,6 +117,8 @@
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.BundleUtils;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
@@ -2278,26 +2278,31 @@
@Override
public void setUserName(@UserIdInt int userId, String name) {
checkManageUsersPermission("rename users");
- boolean changed = false;
synchronized (mPackagesLock) {
UserData userData = getUserDataNoChecks(userId);
if (userData == null || userData.info.partial) {
- Slog.w(LOG_TAG, "setUserName: unknown user #" + userId);
+ Slogf.w(LOG_TAG, "setUserName: unknown user #%d", userId);
return;
}
- if (name != null && !name.equals(userData.info.name)) {
- userData.info.name = name;
- writeUserLP(userData);
- changed = true;
+ if (Objects.equals(name, userData.info.name)) {
+ Slogf.i(LOG_TAG, "setUserName: ignoring for user #%d as it didn't change (%s)",
+ userId, getRedacted(name));
+ return;
}
+ if (name == null) {
+ Slogf.i(LOG_TAG, "setUserName: resetting name of user #%d", userId);
+ } else {
+ Slogf.i(LOG_TAG, "setUserName: setting name of user #%d to %s", userId,
+ getRedacted(name));
+ }
+ userData.info.name = name;
+ writeUserLP(userData);
}
- if (changed) {
- final long ident = Binder.clearCallingIdentity();
- try {
- sendUserInfoChangedBroadcast(userId);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ sendUserInfoChangedBroadcast(userId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -4661,9 +4666,12 @@
storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
t.traceEnd();
+ // Only prepare DE storage here. CE storage will be prepared later, when the user is
+ // unlocked. We do this to ensure that CE storage isn't prepared before the CE key is
+ // saved to disk. This also matches what is done for user 0.
t.traceBegin("prepareUserData");
mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
- StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+ StorageManager.FLAG_STORAGE_DE);
t.traceEnd();
t.traceBegin("LSS.createNewUser");
@@ -6108,6 +6116,11 @@
return RESTRICTIONS_FILE_PREFIX + packageName + XML_SUFFIX;
}
+ @Nullable
+ private static String getRedacted(@Nullable String string) {
+ return string == null ? null : string.length() + "_chars";
+ }
+
@Override
public void setSeedAccountData(@UserIdInt int userId, String accountName, String accountType,
PersistableBundle accountOptions, boolean persist) {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 016c1cb..d1f3341e 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -40,11 +40,11 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.BundleUtils;
import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 37abeac..8588267 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -62,12 +62,12 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.pm.KnownPackages;
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermission.java b/services/core/java/com/android/server/pm/permission/LegacyPermission.java
index 5f8f342..d8b4faa 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermission.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermission.java
@@ -21,9 +21,9 @@
import android.annotation.Nullable;
import android.content.pm.PermissionInfo;
import android.util.Log;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.DumpState;
import com.android.server.pm.PackageManagerService;
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionSettings.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionSettings.java
index f63600a..fc6d202 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionSettings.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionSettings.java
@@ -21,11 +21,11 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.DumpState;
import com.android.server.pm.PackageManagerService;
diff --git a/services/core/java/com/android/server/pm/pkg/SuspendParams.java b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
index 0926ba2..dc48a33 100644
--- a/services/core/java/com/android/server/pm/pkg/SuspendParams.java
+++ b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
@@ -21,8 +21,9 @@
import android.os.BaseBundle;
import android.os.PersistableBundle;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java
index 4bad102..9fb8297 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java
@@ -23,10 +23,10 @@
import android.content.pm.PackageManager;
import android.util.ArrayMap;
import android.util.SparseIntArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.SettingsXml;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 1714086..53ee189 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -33,9 +33,9 @@
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.Pair;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.Computer;
import com.android.server.pm.PackageSetting;
import com.android.server.pm.Settings;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
index e803457..ac6d795 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
@@ -27,9 +27,9 @@
import android.util.ArraySet;
import android.util.PackageUtils;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.SettingsXml;
import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 400af36..595c34c 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -46,11 +46,11 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.compat.PlatformCompat;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
index cde72cd..d256830 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -23,11 +23,11 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.Computer;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a6fac4d..c4e122d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4131,6 +4131,9 @@
case KeyEvent.KEYCODE_DEMO_APP_2:
case KeyEvent.KEYCODE_DEMO_APP_3:
case KeyEvent.KEYCODE_DEMO_APP_4: {
+ // TODO(b/254604589): Dispatch KeyEvent to System UI.
+ sendSystemKeyToStatusBarAsync(keyCode);
+
// Just drop if keys are not intercepted for direct key.
result &= ~ACTION_PASS_TO_USER;
break;
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 69fb22c..1fe82f4 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -22,8 +22,8 @@
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.trust.TrustManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.DisplayManagerInternal;
@@ -796,18 +796,19 @@
}
if (mActivityManagerInternal.isSystemReady()) {
- mContext.sendOrderedBroadcastAsUser(mScreenOnIntent, UserHandle.ALL, null,
- AppOpsManager.OP_NONE, mScreenOnOptions, mWakeUpBroadcastDone, mHandler,
- 0, null, null);
+ final boolean ordered = !mActivityManagerInternal.isModernQueueEnabled();
+ mActivityManagerInternal.broadcastIntent(mScreenOnIntent, mWakeUpBroadcastDone,
+ null, ordered, UserHandle.USER_ALL, null, null, mScreenOnOptions);
} else {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1);
sendNextBroadcast();
}
}
- private final BroadcastReceiver mWakeUpBroadcastDone = new BroadcastReceiver() {
+ private final IIntentReceiver mWakeUpBroadcastDone = new IIntentReceiver.Stub() {
@Override
- public void onReceive(Context context, Intent intent) {
+ public void performReceive(Intent intent, int resultCode, String data, Bundle extras,
+ boolean ordered, boolean sticky, int sendingUser) {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 1,
SystemClock.uptimeMillis() - mBroadcastStartTime, 1);
sendNextBroadcast();
@@ -820,18 +821,19 @@
}
if (mActivityManagerInternal.isSystemReady()) {
- mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null,
- AppOpsManager.OP_NONE, mScreenOffOptions, mGoToSleepBroadcastDone, mHandler,
- 0, null, null);
+ final boolean ordered = !mActivityManagerInternal.isModernQueueEnabled();
+ mActivityManagerInternal.broadcastIntent(mScreenOffIntent, mGoToSleepBroadcastDone,
+ null, ordered, UserHandle.USER_ALL, null, null, mScreenOffOptions);
} else {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);
sendNextBroadcast();
}
}
- private final BroadcastReceiver mGoToSleepBroadcastDone = new BroadcastReceiver() {
+ private final IIntentReceiver mGoToSleepBroadcastDone = new IIntentReceiver.Stub() {
@Override
- public void onReceive(Context context, Intent intent) {
+ public void performReceive(Intent intent, int resultCode, String data, Bundle extras,
+ boolean ordered, boolean sticky, int sendingUser) {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 0,
SystemClock.uptimeMillis() - mBroadcastStartTime, 1);
sendNextBroadcast();
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 1e5b498..916df89 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -99,8 +99,6 @@
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TimeUtils;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.Display;
@@ -130,6 +128,8 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.net.module.util.NetworkCapabilitiesUtils;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
index 50cb33c..0d7a140 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
@@ -25,11 +25,11 @@
import android.util.Log;
import android.util.LongArray;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/sensorprivacy/AllSensorStateController.java b/services/core/java/com/android/server/sensorprivacy/AllSensorStateController.java
index f797f09..58b2443 100644
--- a/services/core/java/com/android/server/sensorprivacy/AllSensorStateController.java
+++ b/services/core/java/com/android/server/sensorprivacy/AllSensorStateController.java
@@ -21,13 +21,13 @@
import android.os.Handler;
import android.util.AtomicFile;
import android.util.Log;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.IoThread;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/sensorprivacy/PersistedState.java b/services/core/java/com/android/server/sensorprivacy/PersistedState.java
index e79efdb8..85ec101 100644
--- a/services/core/java/com/android/server/sensorprivacy/PersistedState.java
+++ b/services/core/java/com/android/server/sensorprivacy/PersistedState.java
@@ -28,14 +28,14 @@
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.util.function.QuadConsumer;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index fc77ef1..dad3a78 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -47,11 +47,11 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseLongArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.Installer;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/tv/PersistentDataStore.java b/services/core/java/com/android/server/tv/PersistentDataStore.java
index 72556a7..f8a9988 100644
--- a/services/core/java/com/android/server/tv/PersistentDataStore.java
+++ b/services/core/java/com/android/server/tv/PersistentDataStore.java
@@ -26,11 +26,11 @@
import android.text.TextUtils;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java b/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java
index 7f49eea..39df450 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java
@@ -20,10 +20,10 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 6aa06e8..01fdc88 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -71,14 +71,14 @@
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 5f420bf..abb57bc 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -99,8 +99,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.Display;
import android.view.DisplayInfo;
@@ -111,6 +109,8 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.JournaledFile;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.EventLogTags;
import com.android.server.FgThread;
import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 81bb3a1..17a9a63 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -312,8 +312,6 @@
import android.util.MergedConfiguration;
import android.util.Slog;
import android.util.TimeUtils;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
import android.view.AppTransitionAnimationSpec;
import android.view.DisplayInfo;
@@ -349,6 +347,8 @@
import com.android.internal.policy.AttributeCache;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.am.AppTimeTracker;
import com.android.server.am.PendingIntentRecord;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 416d546..ecc43f7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -460,7 +460,6 @@
KeyguardController mKeyguardController;
private final ClientLifecycleManager mLifecycleManager;
- @Nullable
final BackNavigationController mBackNavigationController;
private TaskChangeNotificationController mTaskChangeNotificationController;
@@ -847,8 +846,7 @@
mTaskOrganizerController = mWindowOrganizerController.mTaskOrganizerController;
mTaskFragmentOrganizerController =
mWindowOrganizerController.mTaskFragmentOrganizerController;
- mBackNavigationController = BackNavigationController.isEnabled()
- ? new BackNavigationController() : null;
+ mBackNavigationController = new BackNavigationController();
}
public void onSystemReady() {
@@ -1031,9 +1029,7 @@
mLockTaskController.setWindowManager(wm);
mTaskSupervisor.setWindowManager(wm);
mRootWindowContainer.setWindowManager(wm);
- if (mBackNavigationController != null) {
- mBackNavigationController.setWindowManager(wm);
- }
+ mBackNavigationController.setWindowManager(wm);
}
}
@@ -1852,9 +1848,6 @@
IWindowFocusObserver observer, BackAnimationAdapter adapter) {
mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
"startBackNavigation()");
- if (mBackNavigationController == null) {
- return null;
- }
return mBackNavigationController.startBackNavigation(observer, adapter);
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index d2c098b..a487797 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1448,6 +1448,12 @@
|| transit == TRANSIT_OLD_ACTIVITY_RELAUNCH;
}
+ static boolean isTaskFragmentTransitOld(@TransitionOldType int transit) {
+ return transit == TRANSIT_OLD_TASK_FRAGMENT_OPEN
+ || transit == TRANSIT_OLD_TASK_FRAGMENT_CLOSE
+ || transit == TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+ }
+
static boolean isChangeTransitOld(@TransitionOldType int transit) {
return transit == TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE
|| transit == TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index 5a24099..d22c38e 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -31,10 +31,11 @@
import android.util.AtomicFile;
import android.util.DisplayMetrics;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 30399ed..1cb83f12 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -65,12 +65,12 @@
// TODO (b/241808055) Find a appropriate time to remove during refactor
// Execute back animation with legacy transition system. Temporary flag for easier debugging.
static final boolean ENABLE_SHELL_TRANSITIONS = WindowManagerService.sEnableShellTransitions;
+
/**
- * Returns true if the back predictability feature is enabled
+ * true if the back predictability feature is enabled
*/
- static boolean isEnabled() {
- return SystemProperties.getInt("persist.wm.debug.predictive_back", 1) != 0;
- }
+ static final boolean sPredictBackEnable =
+ SystemProperties.getBoolean("persist.wm.debug.predictive_back", true);
static boolean isScreenshotEnabled() {
return SystemProperties.getInt("persist.wm.debug.predictive_back_screenshot", 0) != 0;
@@ -88,6 +88,9 @@
@Nullable
BackNavigationInfo startBackNavigation(
IWindowFocusObserver observer, BackAnimationAdapter adapter) {
+ if (!sPredictBackEnable) {
+ return null;
+ }
final WindowManagerService wmService = mWindowManagerService;
mFocusObserver = observer;
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index 6f19450..a035948 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -44,11 +44,11 @@
import android.util.DisplayMetrics;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 739f41f..8b34443 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -47,7 +47,6 @@
import static android.view.Display.isSuspendedState;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
@@ -55,6 +54,7 @@
import static android.view.View.GONE;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
@@ -216,7 +216,6 @@
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
import android.view.MagnificationSpec;
import android.view.PrivacyIndicatorBounds;
import android.view.RemoteAnimationDefinition;
@@ -227,6 +226,7 @@
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
@@ -788,11 +788,11 @@
// higher window hierarchy, we don't give it focus if the next IME layering target
// doesn't request IME visible.
if (w.mIsImWindow && w.isChildWindow() && (mImeLayeringTarget == null
- || !mImeLayeringTarget.getRequestedVisibility(ITYPE_IME))) {
+ || !mImeLayeringTarget.isRequestedVisible(ime()))) {
return false;
}
if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null
- && !mImeLayeringTarget.getRequestedVisibility(ITYPE_IME)
+ && !mImeLayeringTarget.isRequestedVisible(ime())
&& !mImeLayeringTarget.isVisibleRequested()) {
return false;
}
@@ -2059,7 +2059,7 @@
// is opened for logging metrics.
if (mWmService.mAccessibilityController.hasCallbacks()) {
final boolean isImeShow = mImeControlTarget != null
- && mImeControlTarget.getRequestedVisibility(ITYPE_IME);
+ && mImeControlTarget.isRequestedVisible(ime());
mWmService.mAccessibilityController.updateImeVisibilityIfNeeded(mDisplayId, isImeShow);
}
}
@@ -5662,7 +5662,7 @@
final int type = win.mAttrs.type;
final int privateFlags = win.mAttrs.privateFlags;
final boolean stickyHideNav =
- !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR)
+ !win.isRequestedVisible(navigationBars())
&& win.mAttrs.insetsFlags.behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
return (!stickyHideNav || ignoreRequest) && type != TYPE_INPUT_METHOD
&& type != TYPE_NOTIFICATION_SHADE && win.getActivityType() != ACTIVITY_TYPE_HOME
@@ -6672,7 +6672,7 @@
class RemoteInsetsControlTarget implements InsetsControlTarget {
private final IDisplayWindowInsetsController mRemoteInsetsController;
- private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
private final boolean mCanShowTransient;
RemoteInsetsControlTarget(IDisplayWindowInsetsController controller) {
@@ -6685,12 +6685,12 @@
* Notifies the remote insets controller that the top focused window has changed.
*
* @param component The application component that is open in the top focussed window.
- * @param requestedVisibilities The insets visibilities requested by the focussed window.
+ * @param requestedVisibleTypes The insets types requested visible by the focused window.
*/
void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ @InsetsType int requestedVisibleTypes) {
try {
- mRemoteInsetsController.topFocusedWindowChanged(component, requestedVisibilities);
+ mRemoteInsetsController.topFocusedWindowChanged(component, requestedVisibleTypes);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver package in top focused window change", e);
}
@@ -6726,7 +6726,7 @@
}
@Override
- public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+ public void hideInsets(@InsetsType int types, boolean fromIme) {
try {
mRemoteInsetsController.hideInsets(types, fromIme);
} catch (RemoteException e) {
@@ -6740,15 +6740,25 @@
}
@Override
- public boolean getRequestedVisibility(@InternalInsetsType int type) {
- if (type == ITYPE_IME) {
+ public boolean isRequestedVisible(@InsetsType int types) {
+ if (types == ime()) {
return getInsetsStateController().getImeSourceProvider().isImeShowing();
}
- return mRequestedVisibilities.getVisibility(type);
+ return (mRequestedVisibleTypes & types) != 0;
}
- void setRequestedVisibilities(InsetsVisibilities requestedVisibilities) {
- mRequestedVisibilities.set(requestedVisibilities);
+ @Override
+ public @InsetsType int getRequestedVisibleTypes() {
+ return mRequestedVisibleTypes;
+ }
+
+ /**
+ * @see #getRequestedVisibleTypes()
+ */
+ void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
+ if (mRequestedVisibleTypes != requestedVisibleTypes) {
+ mRequestedVisibleTypes = requestedVisibleTypes;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 442777a..8723994 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -19,14 +19,11 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.Display.TYPE_INTERNAL;
-import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
+import static android.view.InsetsFrameProvider.SOURCE_FRAME;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -47,7 +44,6 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
@@ -127,6 +123,7 @@
import android.view.Surface;
import android.view.View;
import android.view.ViewDebug;
+import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowLayout;
@@ -209,14 +206,11 @@
private StatusBarManagerInternal mStatusBarManagerInternal;
@Px
- private int mBottomGestureAdditionalInset;
- @Px
private int mLeftGestureInset;
@Px
private int mRightGestureInset;
private boolean mCanSystemBarsBeShownByUser;
- private boolean mNavButtonForcedVisible;
StatusBarManagerInternal getStatusBarManagerInternal() {
synchronized (mServiceAcquireLock) {
@@ -240,7 +234,6 @@
private volatile boolean mHasNavigationBar;
// Can the navigation bar ever move to the side?
private volatile boolean mNavigationBarCanMove;
- private volatile boolean mNavigationBarLetsThroughTaps;
private volatile boolean mNavigationBarAlwaysShowOnSideGesture;
// Written by vr manager thread, only read in this class.
@@ -324,6 +317,7 @@
private int mLastDisableFlags;
private int mLastAppearance;
private int mLastBehavior;
+ private int mLastRequestedVisibleTypes = Type.defaultVisible();
private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
private AppearanceRegion[] mLastStatusBarAppearanceRegions;
private LetterboxDetails[] mLastLetterboxDetails;
@@ -360,8 +354,6 @@
private PointerLocationView mPointerLocationView;
- private int mDisplayCutoutTouchableRegionSize;
-
private RefreshRatePolicy mRefreshRatePolicy;
/**
@@ -1150,71 +1142,9 @@
break;
case TYPE_NAVIGATION_BAR:
mNavigationBar = win;
- final TriConsumer<DisplayFrames, WindowContainer, Rect> navFrameProvider =
- (displayFrames, windowContainer, inOutFrame) -> {
- if (!mNavButtonForcedVisible) {
- final LayoutParams lp =
- win.mAttrs.forRotation(displayFrames.mRotation);
- if (lp.providedInsets != null) {
- for (InsetsFrameProvider provider : lp.providedInsets) {
- if (provider.type != ITYPE_NAVIGATION_BAR) {
- continue;
- }
- InsetsFrameProvider.calculateInsetsFrame(
- displayFrames.mUnrestricted,
- win.getBounds(), displayFrames.mDisplayCutoutSafe,
- inOutFrame, provider.source,
- provider.insetsSize, lp.privateFlags,
- provider.minimalInsetsSizeInDisplayCutoutSafe);
- }
- }
- inOutFrame.inset(win.mGivenContentInsets);
- }
- };
- final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> imeOverride =
- new SparseArray<>();
- // For IME, we don't modify the frame.
- imeOverride.put(TYPE_INPUT_METHOD, null);
- mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, win,
- navFrameProvider, imeOverride);
-
- mDisplayContent.setInsetProvider(ITYPE_BOTTOM_MANDATORY_GESTURES, win,
- (displayFrames, windowContainer, inOutFrame) -> {
- inOutFrame.top -= mBottomGestureAdditionalInset;
- });
- mDisplayContent.setInsetProvider(ITYPE_LEFT_GESTURES, win,
- (displayFrames, windowContainer, inOutFrame) -> {
- final int leftSafeInset =
- Math.max(displayFrames.mDisplayCutoutSafe.left, 0);
- inOutFrame.left = 0;
- inOutFrame.top = 0;
- inOutFrame.bottom = displayFrames.mHeight;
- inOutFrame.right = leftSafeInset + mLeftGestureInset;
- });
- mDisplayContent.setInsetProvider(ITYPE_RIGHT_GESTURES, win,
- (displayFrames, windowContainer, inOutFrame) -> {
- final int rightSafeInset =
- Math.min(displayFrames.mDisplayCutoutSafe.right,
- displayFrames.mUnrestricted.right);
- inOutFrame.left = rightSafeInset - mRightGestureInset;
- inOutFrame.top = 0;
- inOutFrame.bottom = displayFrames.mHeight;
- inOutFrame.right = displayFrames.mWidth;
- });
- mDisplayContent.setInsetProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT, win,
- (displayFrames, windowContainer, inOutFrame) -> {
- if ((win.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0
- || mNavigationBarLetsThroughTaps) {
- inOutFrame.setEmpty();
- }
- });
- mInsetsSourceWindowsExceptIme.add(win);
- if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
break;
}
- // TODO(b/239145252): Temporarily skip the navigation bar as it is still with the hard-coded
- // logic.
- if (attrs.providedInsets != null && attrs.type != TYPE_NAVIGATION_BAR) {
+ if (attrs.providedInsets != null) {
for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
final InsetsFrameProvider provider = attrs.providedInsets[i];
switch (provider.type) {
@@ -1242,24 +1172,8 @@
// The index of the provider and corresponding insets types cannot change at
// runtime as ensured in WMS. Make use of the index in the provider directly
// to access the latest provided size at runtime.
- final int index = i;
final TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider =
- provider.insetsSize != null
- ? (displayFrames, windowContainer, inOutFrame) -> {
- inOutFrame.inset(win.mGivenContentInsets);
- final LayoutParams lp =
- win.mAttrs.forRotation(displayFrames.mRotation);
- final InsetsFrameProvider ifp =
- win.mAttrs.forRotation(displayFrames.mRotation)
- .providedInsets[index];
- InsetsFrameProvider.calculateInsetsFrame(
- displayFrames.mUnrestricted,
- windowContainer.getBounds(),
- displayFrames.mDisplayCutoutSafe,
- inOutFrame, ifp.source,
- ifp.insetsSize, lp.privateFlags,
- ifp.minimalInsetsSizeInDisplayCutoutSafe);
- } : null;
+ getFrameProvider(win, provider, i);
final InsetsFrameProvider.InsetsSizeOverride[] overrides =
provider.insetsSizeOverrides;
final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>>
@@ -1267,27 +1181,10 @@
if (overrides != null) {
overrideProviders = new SparseArray<>();
for (int j = overrides.length - 1; j >= 0; j--) {
- final int overrideIndex = j;
final TriConsumer<DisplayFrames, WindowContainer, Rect>
overrideFrameProvider =
- (displayFrames, windowContainer, inOutFrame) -> {
- final LayoutParams lp =
- win.mAttrs.forRotation(
- displayFrames.mRotation);
- final InsetsFrameProvider ifp =
- win.mAttrs.providedInsets[index];
- InsetsFrameProvider.calculateInsetsFrame(
- displayFrames.mUnrestricted,
- windowContainer.getBounds(),
- displayFrames.mDisplayCutoutSafe,
- inOutFrame, ifp.source,
- ifp.insetsSizeOverrides[
- overrideIndex].insetsSize,
- lp.privateFlags,
- null);
- };
- overrideProviders.put(overrides[j].windowType,
- overrideFrameProvider);
+ getOverrideFrameProvider(win, i, j);
+ overrideProviders.put(overrides[j].windowType, overrideFrameProvider);
}
} else {
overrideProviders = null;
@@ -1299,6 +1196,36 @@
}
}
+ @Nullable
+ private TriConsumer<DisplayFrames, WindowContainer, Rect> getFrameProvider(WindowState win,
+ InsetsFrameProvider provider, int index) {
+ if (provider.insetsSize == null && provider.source == SOURCE_FRAME) {
+ return null;
+ }
+ return (displayFrames, windowContainer, inOutFrame) -> {
+ inOutFrame.inset(win.mGivenContentInsets);
+ final LayoutParams lp = win.mAttrs.forRotation(displayFrames.mRotation);
+ final InsetsFrameProvider ifp = lp.providedInsets[index];
+ InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted,
+ windowContainer.getBounds(), displayFrames.mDisplayCutoutSafe, inOutFrame,
+ ifp.source, ifp.insetsSize, lp.privateFlags,
+ ifp.minimalInsetsSizeInDisplayCutoutSafe);
+ };
+ }
+
+ @NonNull
+ private TriConsumer<DisplayFrames, WindowContainer, Rect> getOverrideFrameProvider(
+ WindowState win, int index, int overrideIndex) {
+ return (displayFrames, windowContainer, inOutFrame) -> {
+ final LayoutParams lp = win.mAttrs.forRotation(displayFrames.mRotation);
+ final InsetsFrameProvider ifp = lp.providedInsets[index];
+ InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted,
+ windowContainer.getBounds(), displayFrames.mDisplayCutoutSafe, inOutFrame,
+ ifp.source, ifp.insetsSizeOverrides[overrideIndex].insetsSize, lp.privateFlags,
+ null);
+ };
+ }
+
@WindowManagerPolicy.AltBarPosition
private int getAltBarPosition(WindowManager.LayoutParams params) {
switch (params.gravity) {
@@ -1386,16 +1313,6 @@
mInsetsSourceWindowsExceptIme.remove(win);
}
- private int getStatusBarHeight(DisplayFrames displayFrames) {
- int statusBarHeight;
- if (mStatusBar != null) {
- statusBarHeight = mStatusBar.mAttrs.forRotation(displayFrames.mRotation).height;
- } else {
- statusBarHeight = 0;
- }
- return Math.max(statusBarHeight, displayFrames.mDisplayCutoutSafe.top);
- }
-
WindowState getStatusBar() {
return mStatusBar != null ? mStatusBar : mStatusBarAlt;
}
@@ -1551,7 +1468,7 @@
mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation),
displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe,
displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
- UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale,
+ UNSPECIFIED_LENGTH, win.getRequestedVisibleTypes(), win.mGlobalScale,
sTmpClientFrames);
final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources();
final InsetsState state = displayFrames.mInsetsState;
@@ -1598,7 +1515,7 @@
mWindowLayout.computeFrames(attrs, win.getInsetsState(), displayFrames.mDisplayCutoutSafe,
win.getBounds(), win.getWindowingMode(), requestedWidth, requestedHeight,
- win.getRequestedVisibilities(), win.mGlobalScale, sTmpClientFrames);
+ win.getRequestedVisibleTypes(), win.mGlobalScale, sTmpClientFrames);
win.setFrames(sTmpClientFrames, win.mRequestedWidth, win.mRequestedHeight);
}
@@ -1861,7 +1778,7 @@
if (mTopFullscreenOpaqueWindowState == null || mForceShowSystemBars) {
return false;
}
- return !mTopFullscreenOpaqueWindowState.getRequestedVisibility(ITYPE_STATUS_BAR);
+ return !mTopFullscreenOpaqueWindowState.isRequestedVisible(Type.statusBars());
}
/**
@@ -1892,27 +1809,12 @@
final Resources res = getCurrentUserResources();
final int portraitRotation = displayRotation.getPortraitRotation();
- if (hasStatusBar()) {
- mDisplayCutoutTouchableRegionSize = res.getDimensionPixelSize(
- R.dimen.display_cutout_touchable_region_size);
- } else {
- mDisplayCutoutTouchableRegionSize = 0;
- }
-
mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode);
mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res);
- mNavButtonForcedVisible =
- mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
- mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough);
mNavigationBarAlwaysShowOnSideGesture =
res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture);
- // This should calculate how much above the frame we accept gestures.
- mBottomGestureAdditionalInset =
- res.getDimensionPixelSize(R.dimen.navigation_bar_gesture_height)
- - getNavigationBarFrameHeight(portraitRotation);
-
updateConfigurationAndScreenSizeDependentBehaviors();
final boolean shouldAttach =
@@ -2221,17 +2123,8 @@
return;
}
- final @InsetsType int restorePositionTypes =
- (controlTarget.getRequestedVisibility(ITYPE_NAVIGATION_BAR)
- ? Type.navigationBars() : 0)
- | (controlTarget.getRequestedVisibility(ITYPE_STATUS_BAR)
- ? Type.statusBars() : 0)
- | (mExtraNavBarAlt != null && controlTarget.getRequestedVisibility(
- ITYPE_EXTRA_NAVIGATION_BAR)
- ? Type.navigationBars() : 0)
- | (mClimateBarAlt != null && controlTarget.getRequestedVisibility(
- ITYPE_CLIMATE_BAR)
- ? Type.statusBars() : 0);
+ final @InsetsType int restorePositionTypes = (Type.statusBars() | Type.navigationBars())
+ & controlTarget.getRequestedVisibleTypes();
if (swipeTarget == mNavigationBar
&& (restorePositionTypes & Type.navigationBars()) != 0) {
@@ -2325,8 +2218,8 @@
navColorWin) | opaqueAppearance;
final int behavior = win.mAttrs.insetsFlags.behavior;
final String focusedApp = win.mAttrs.packageName;
- final boolean isFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR)
- || !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
+ final boolean isFullscreen = !win.isRequestedVisible(Type.statusBars())
+ || !win.isRequestedVisible(Type.navigationBars());
final AppearanceRegion[] statusBarAppearanceRegions =
new AppearanceRegion[mStatusBarAppearanceRegionList.size()];
mStatusBarAppearanceRegionList.toArray(statusBarAppearanceRegions);
@@ -2336,11 +2229,12 @@
callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags,
cause));
}
+ final @InsetsType int requestedVisibleTypes = win.getRequestedVisibleTypes();
final LetterboxDetails[] letterboxDetails = new LetterboxDetails[mLetterboxDetails.size()];
mLetterboxDetails.toArray(letterboxDetails);
if (mLastAppearance == appearance
&& mLastBehavior == behavior
- && mRequestedVisibilities.equals(win.getRequestedVisibilities())
+ && mLastRequestedVisibleTypes == requestedVisibleTypes
&& Objects.equals(mFocusedApp, focusedApp)
&& mLastFocusIsFullscreen == isFullscreen
&& Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)
@@ -2353,9 +2247,12 @@
isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
}
final InsetsVisibilities requestedVisibilities =
- new InsetsVisibilities(win.getRequestedVisibilities());
+ mLastRequestedVisibleTypes == requestedVisibleTypes
+ ? mRequestedVisibilities
+ : toInsetsVisibilities(requestedVisibleTypes);
mLastAppearance = appearance;
mLastBehavior = behavior;
+ mLastRequestedVisibleTypes = requestedVisibleTypes;
mRequestedVisibilities = requestedVisibilities;
mFocusedApp = focusedApp;
mLastFocusIsFullscreen = isFullscreen;
@@ -2366,6 +2263,20 @@
requestedVisibilities, focusedApp, letterboxDetails));
}
+ // TODO (253420890): Remove this when removing mRequestedVisibilities.
+ private static InsetsVisibilities toInsetsVisibilities(@InsetsType int requestedVisibleTypes) {
+ final @InsetsType int defaultVisibleTypes = WindowInsets.Type.defaultVisible();
+ final InsetsVisibilities insetsVisibilities = new InsetsVisibilities();
+ for (@InternalInsetsType int i = InsetsState.SIZE - 1; i >= 0; i--) {
+ @InsetsType int type = InsetsState.toPublicType(i);
+ if ((type & (requestedVisibleTypes ^ defaultVisibleTypes)) != 0) {
+ // We only set the visibility if it is different from the default one.
+ insetsVisibilities.setVisibility(i, (type & requestedVisibleTypes) != 0);
+ }
+ }
+ return insetsVisibilities;
+ }
+
private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
mHandler.post(() -> {
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
@@ -2456,7 +2367,7 @@
appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible,
freeformRootTaskVisible);
- final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
+ final boolean requestHideNavBar = !win.isRequestedVisible(Type.navigationBars());
final long now = SystemClock.uptimeMillis();
final boolean pendingPanic = mPendingPanicGestureUptime != 0
&& now - mPendingPanicGestureUptime <= PANIC_GESTURE_EXPIRATION;
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 4a70fa3..1abb0a1 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -30,14 +30,14 @@
import android.os.Environment;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.DisplayAddress;
import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 14a1cd0..38eca35 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -34,6 +34,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
+import android.view.InsetsState;
import android.view.WindowInsets;
import android.window.TaskSnapshot;
@@ -104,7 +105,7 @@
@Override
protected boolean updateClientVisibility(InsetsControlTarget caller) {
boolean changed = super.updateClientVisibility(caller);
- if (changed && caller.getRequestedVisibility(mSource.getType())) {
+ if (changed && caller.isRequestedVisible(InsetsState.toPublicType(mSource.getType()))) {
reportImeDrawnForOrganizer(caller);
}
return changed;
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index 287dd74..d35b7c3 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -17,8 +17,7 @@
package com.android.server.wm;
import android.inputmethodservice.InputMethodService;
-import android.view.InsetsState;
-import android.view.InsetsState.InternalInsetsType;
+import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
/**
@@ -40,10 +39,17 @@
}
/**
- * @return The requested visibility of this target.
+ * @return {@code true} if any of the {@link InsetsType} is requested visible by this target.
*/
- default boolean getRequestedVisibility(@InternalInsetsType int type) {
- return InsetsState.getDefaultVisibility(type);
+ default boolean isRequestedVisible(@InsetsType int types) {
+ return (WindowInsets.Type.defaultVisible() & types) != 0;
+ }
+
+ /**
+ * @return {@link InsetsType}s which are requested visible by this target.
+ */
+ default @InsetsType int getRequestedVisibleTypes() {
+ return WindowInsets.Type.defaultVisible();
}
/**
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 2de8faf..b9fa80c 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -177,8 +177,8 @@
: navControlTarget == notificationShade
? getNavControlTarget(topApp, true /* fake */)
: null);
- mStatusBar.updateVisibility(statusControlTarget, ITYPE_STATUS_BAR);
- mNavBar.updateVisibility(navControlTarget, ITYPE_NAVIGATION_BAR);
+ mStatusBar.updateVisibility(statusControlTarget, Type.statusBars());
+ mNavBar.updateVisibility(navControlTarget, Type.navigationBars());
}
boolean isHidden(@InternalInsetsType int type) {
@@ -455,7 +455,7 @@
if (originalImeSource != null) {
final boolean imeVisibility =
- w.mActivityRecord.mLastImeShown || w.getRequestedVisibility(ITYPE_IME);
+ w.mActivityRecord.mLastImeShown || w.isRequestedVisible(Type.ime());
final InsetsState state = copyState ? new InsetsState(originalState)
: originalState;
final InsetsSource imeSource = new InsetsSource(originalImeSource);
@@ -501,11 +501,11 @@
private void checkAbortTransient(InsetsControlTarget caller) {
if (mShowingTransientTypes.size() != 0) {
final IntArray abortTypes = new IntArray();
- final boolean imeRequestedVisible = caller.getRequestedVisibility(ITYPE_IME);
+ final boolean imeRequestedVisible = caller.isRequestedVisible(Type.ime());
for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) {
final @InternalInsetsType int type = mShowingTransientTypes.get(i);
if ((mStateController.isFakeTarget(type, caller)
- && caller.getRequestedVisibility(type))
+ && caller.isRequestedVisible(InsetsState.toPublicType(type)))
|| (type == ITYPE_NAVIGATION_BAR && imeRequestedVisible)) {
mShowingTransientTypes.remove(i);
abortTypes.add(type);
@@ -552,7 +552,7 @@
ComponentName component = focusedWin.mActivityRecord != null
? focusedWin.mActivityRecord.mActivityComponent : null;
mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
- component, focusedWin.getRequestedVisibilities());
+ component, focusedWin.getRequestedVisibleTypes());
return mDisplayContent.mRemoteInsetsControlTarget;
}
if (mPolicy.areSystemBarsForcedShownLw()) {
@@ -612,7 +612,7 @@
ComponentName component = focusedWin.mActivityRecord != null
? focusedWin.mActivityRecord.mActivityComponent : null;
mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
- component, focusedWin.getRequestedVisibilities());
+ component, focusedWin.getRequestedVisibleTypes());
return mDisplayContent.mRemoteInsetsControlTarget;
}
if (mPolicy.areSystemBarsForcedShownLw()) {
@@ -734,8 +734,8 @@
}
private void updateVisibility(@Nullable InsetsControlTarget controlTarget,
- @InternalInsetsType int type) {
- setVisible(controlTarget == null || controlTarget.getRequestedVisibility(type));
+ @Type.InsetsType int type) {
+ setVisible(controlTarget == null || controlTarget.isRequestedVisible(type));
}
private void setVisible(boolean visible) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index bf4b65d..5b205f0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -48,6 +48,7 @@
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -173,6 +174,7 @@
mWindowContainer = windowContainer;
// TODO: remove the frame provider for non-WindowState container.
mFrameProvider = frameProvider;
+ mOverrideFrames.clear();
mOverrideFrameProviders = overrideFrameProviders;
if (windowContainer == null) {
setServerVisible(false);
@@ -234,6 +236,8 @@
updateSourceFrameForServerVisibility();
if (mOverrideFrameProviders != null) {
+ // Not necessary to clear the mOverrideFrames here. It will be cleared every time the
+ // override frame provider updates.
for (int i = mOverrideFrameProviders.size() - 1; i >= 0; i--) {
final int windowType = mOverrideFrameProviders.keyAt(i);
final Rect overrideFrame;
@@ -455,8 +459,9 @@
}
final Point surfacePosition = getWindowFrameSurfacePosition();
mAdapter = new ControlAdapter(surfacePosition);
- if (getSource().getType() == ITYPE_IME) {
- setClientVisible(target.getRequestedVisibility(mSource.getType()));
+ final int type = getSource().getType();
+ if (type == ITYPE_IME) {
+ setClientVisible(target.isRequestedVisible(WindowInsets.Type.ime()));
}
final Transaction t = mDisplayContent.getSyncTransaction();
mWindowContainer.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
@@ -469,8 +474,8 @@
final SurfaceControl leash = mAdapter.mCapturedLeash;
mControlTarget = target;
updateVisibility();
- mControl = new InsetsSourceControl(mSource.getType(), leash, mClientVisible,
- surfacePosition, mInsetsHint);
+ mControl = new InsetsSourceControl(type, leash, mClientVisible, surfacePosition,
+ mInsetsHint);
ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
"InsetsSource Control %s for target %s", mControl, mControlTarget);
@@ -488,7 +493,8 @@
}
boolean updateClientVisibility(InsetsControlTarget caller) {
- final boolean requestedVisible = caller.getRequestedVisibility(mSource.getType());
+ final boolean requestedVisible =
+ caller.isRequestedVisible(InsetsState.toPublicType(mSource.getType()));
if (caller != mControlTarget || requestedVisible == mClientVisible) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index be3ceb8..bf511adf0 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -27,12 +27,12 @@
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.pm.PackageList;
import com.android.server.wm.LaunchParamsController.LaunchParams;
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index a469c6b..c19353c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -17,14 +17,17 @@
package com.android.server.wm;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.function.Function;
/** Reads letterbox configs from resources and controls their overrides at runtime. */
final class LetterboxConfiguration {
@@ -156,34 +159,25 @@
// portrait device orientation.
private boolean mIsVerticalReachabilityEnabled;
-
- // Horizontal position of a center of the letterboxed app window which is global to prevent
- // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
- // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
- // LetterboxUiController#getHorizontalPositionMultiplier which is called from
- // ActivityRecord#updateResolvedBoundsPosition.
- // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
- // Overview after changing position in another app.
- @LetterboxHorizontalReachabilityPosition
- private volatile int mLetterboxPositionForHorizontalReachability;
-
- // Vertical position of a center of the letterboxed app window which is global to prevent
- // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
- // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
- // LetterboxUiController#getVerticalPositionMultiplier which is called from
- // ActivityRecord#updateResolvedBoundsPosition.
- // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
- // Overview after changing position in another app.
- @LetterboxVerticalReachabilityPosition
- private volatile int mLetterboxPositionForVerticalReachability;
-
// Whether education is allowed for letterboxed fullscreen apps.
private boolean mIsEducationEnabled;
// Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.
private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled;
+ // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
+ @NonNull
+ private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+
LetterboxConfiguration(Context systemUiContext) {
+ this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext)));
+ }
+
+ @VisibleForTesting
+ LetterboxConfiguration(Context systemUiContext,
+ LetterboxConfigurationPersister letterboxConfigurationPersister) {
mContext = systemUiContext;
mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
R.dimen.config_fixedOrientationLetterboxAspectRatio);
@@ -206,14 +200,14 @@
readLetterboxHorizontalReachabilityPositionFromConfig(mContext);
mDefaultPositionForVerticalReachability =
readLetterboxVerticalReachabilityPositionFromConfig(mContext);
- mLetterboxPositionForHorizontalReachability = mDefaultPositionForHorizontalReachability;
- mLetterboxPositionForVerticalReachability = mDefaultPositionForVerticalReachability;
mIsEducationEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsEducationEnabled);
setDefaultMinAspectRatioForUnresizableApps(mContext.getResources().getFloat(
R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
+ mLetterboxConfigurationPersister = letterboxConfigurationPersister;
+ mLetterboxConfigurationPersister.start();
}
/**
@@ -653,7 +647,9 @@
* <p>The position multiplier is changed after each double tap in the letterbox area.
*/
float getHorizontalMultiplierForReachability() {
- switch (mLetterboxPositionForHorizontalReachability) {
+ final int letterboxPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ switch (letterboxPositionForHorizontalReachability) {
case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
return 0.0f;
case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
@@ -662,10 +658,11 @@
return 1.0f;
default:
throw new AssertionError(
- "Unexpected letterbox position type: "
- + mLetterboxPositionForHorizontalReachability);
+ "Unexpected letterbox position type: "
+ + letterboxPositionForHorizontalReachability);
}
}
+
/*
* Gets vertical position of a center of the letterboxed app window when reachability
* is enabled specified. 0 corresponds to the top side of the screen and 1 to the bottom side.
@@ -673,7 +670,9 @@
* <p>The position multiplier is changed after each double tap in the letterbox area.
*/
float getVerticalMultiplierForReachability() {
- switch (mLetterboxPositionForVerticalReachability) {
+ final int letterboxPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ switch (letterboxPositionForVerticalReachability) {
case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
return 0.0f;
case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
@@ -683,7 +682,7 @@
default:
throw new AssertionError(
"Unexpected letterbox position type: "
- + mLetterboxPositionForVerticalReachability);
+ + letterboxPositionForVerticalReachability);
}
}
@@ -693,7 +692,7 @@
*/
@LetterboxHorizontalReachabilityPosition
int getLetterboxPositionForHorizontalReachability() {
- return mLetterboxPositionForHorizontalReachability;
+ return mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
}
/*
@@ -702,7 +701,7 @@
*/
@LetterboxVerticalReachabilityPosition
int getLetterboxPositionForVerticalReachability() {
- return mLetterboxPositionForVerticalReachability;
+ return mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
}
/** Returns a string representing the given {@link LetterboxHorizontalReachabilityPosition}. */
@@ -742,9 +741,8 @@
* right side.
*/
void movePositionForHorizontalReachabilityToNextRightStop() {
- mLetterboxPositionForHorizontalReachability = Math.min(
- mLetterboxPositionForHorizontalReachability + 1,
- LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT);
+ updatePositionForHorizontalReachability(prev -> Math.min(
+ prev + 1, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT));
}
/**
@@ -752,8 +750,7 @@
* side.
*/
void movePositionForHorizontalReachabilityToNextLeftStop() {
- mLetterboxPositionForHorizontalReachability =
- Math.max(mLetterboxPositionForHorizontalReachability - 1, 0);
+ updatePositionForHorizontalReachability(prev -> Math.max(prev - 1, 0));
}
/**
@@ -761,9 +758,8 @@
* side.
*/
void movePositionForVerticalReachabilityToNextBottomStop() {
- mLetterboxPositionForVerticalReachability = Math.min(
- mLetterboxPositionForVerticalReachability + 1,
- LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM);
+ updatePositionForVerticalReachability(prev -> Math.min(
+ prev + 1, LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM));
}
/**
@@ -771,8 +767,7 @@
* side.
*/
void movePositionForVerticalReachabilityToNextTopStop() {
- mLetterboxPositionForVerticalReachability =
- Math.max(mLetterboxPositionForVerticalReachability - 1, 0);
+ updatePositionForVerticalReachability(prev -> Math.max(prev - 1, 0));
}
/**
@@ -822,4 +817,26 @@
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
}
+ /** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */
+ private void updatePositionForHorizontalReachability(
+ Function<Integer, Integer> newHorizonalPositionFun) {
+ final int letterboxPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int nextHorizontalPosition = newHorizonalPositionFun.apply(
+ letterboxPositionForHorizontalReachability);
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ nextHorizontalPosition);
+ }
+
+ /** Calculates a new letterboxPositionForVerticalReachability value and updates the store */
+ private void updatePositionForVerticalReachability(
+ Function<Integer, Integer> newVerticalPositionFun) {
+ final int letterboxPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ final int nextVerticalPosition = newVerticalPositionFun.apply(
+ letterboxPositionForVerticalReachability);
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ nextVerticalPosition);
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
new file mode 100644
index 0000000..70639b1
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.LetterboxConfiguration.LetterboxHorizontalReachabilityPosition;
+import com.android.server.wm.LetterboxConfiguration.LetterboxVerticalReachabilityPosition;
+import com.android.server.wm.nano.WindowManagerProtos;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Persists the values of letterboxPositionForHorizontalReachability and
+ * letterboxPositionForVerticalReachability for {@link LetterboxConfiguration}.
+ */
+class LetterboxConfigurationPersister {
+
+ private static final String TAG =
+ TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM;
+
+ @VisibleForTesting
+ static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
+
+ private final Context mContext;
+ private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier;
+ private final Supplier<Integer> mDefaultVerticalReachabilitySupplier;
+
+ // Horizontal position of a center of the letterboxed app window which is global to prevent
+ // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+ // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+ // LetterboxUiController#getHorizontalPositionMultiplier which is called from
+ // ActivityRecord#updateResolvedBoundsPosition.
+ @LetterboxHorizontalReachabilityPosition
+ private volatile int mLetterboxPositionForHorizontalReachability;
+
+ // Vertical position of a center of the letterboxed app window which is global to prevent
+ // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+ // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+ // LetterboxUiController#getVerticalPositionMultiplier which is called from
+ // ActivityRecord#updateResolvedBoundsPosition.
+ @LetterboxVerticalReachabilityPosition
+ private volatile int mLetterboxPositionForVerticalReachability;
+
+ @NonNull
+ private final AtomicFile mConfigurationFile;
+
+ @Nullable
+ private final Consumer<String> mCompletionCallback;
+
+ @NonNull
+ private final PersisterQueue mPersisterQueue;
+
+ LetterboxConfigurationPersister(Context systemUiContext,
+ Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ Supplier<Integer> defaultVerticalReachabilitySupplier) {
+ this(systemUiContext, defaultHorizontalReachabilitySupplier,
+ defaultVerticalReachabilitySupplier,
+ Environment.getDataSystemDirectory(), new PersisterQueue(),
+ /* completionCallback */ null);
+ }
+
+ @VisibleForTesting
+ LetterboxConfigurationPersister(Context systemUiContext,
+ Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ Supplier<Integer> defaultVerticalReachabilitySupplier, File configFolder,
+ PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) {
+ mContext = systemUiContext.createDeviceProtectedStorageContext();
+ mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier;
+ mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier;
+ mCompletionCallback = completionCallback;
+ final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME);
+ mConfigurationFile = new AtomicFile(prefFiles);
+ mPersisterQueue = persisterQueue;
+ readCurrentConfiguration();
+ }
+
+ /**
+ * Startes the persistence queue
+ */
+ void start() {
+ mPersisterQueue.startPersisting();
+ }
+
+ /*
+ * Gets the horizontal position of the letterboxed app window when horizontal reachability is
+ * enabled.
+ */
+ @LetterboxHorizontalReachabilityPosition
+ int getLetterboxPositionForHorizontalReachability() {
+ return mLetterboxPositionForHorizontalReachability;
+ }
+
+ /*
+ * Gets the vertical position of the letterboxed app window when vertical reachability is
+ * enabled.
+ */
+ @LetterboxVerticalReachabilityPosition
+ int getLetterboxPositionForVerticalReachability() {
+ return mLetterboxPositionForVerticalReachability;
+ }
+
+ /**
+ * Updates letterboxPositionForVerticalReachability if different from the current value
+ */
+ void setLetterboxPositionForHorizontalReachability(
+ int letterboxPositionForHorizontalReachability) {
+ if (mLetterboxPositionForHorizontalReachability
+ != letterboxPositionForHorizontalReachability) {
+ mLetterboxPositionForHorizontalReachability =
+ letterboxPositionForHorizontalReachability;
+ updateConfiguration();
+ }
+ }
+
+ /**
+ * Updates letterboxPositionForVerticalReachability if different from the current value
+ */
+ void setLetterboxPositionForVerticalReachability(
+ int letterboxPositionForVerticalReachability) {
+ if (mLetterboxPositionForVerticalReachability != letterboxPositionForVerticalReachability) {
+ mLetterboxPositionForVerticalReachability = letterboxPositionForVerticalReachability;
+ updateConfiguration();
+ }
+ }
+
+ @VisibleForTesting
+ void useDefaultValue() {
+ mLetterboxPositionForHorizontalReachability = mDefaultHorizontalReachabilitySupplier.get();
+ mLetterboxPositionForVerticalReachability = mDefaultVerticalReachabilitySupplier.get();
+ }
+
+ private void readCurrentConfiguration() {
+ FileInputStream fis = null;
+ try {
+ fis = mConfigurationFile.openRead();
+ byte[] protoData = readInputStream(fis);
+ final WindowManagerProtos.LetterboxProto letterboxData =
+ WindowManagerProtos.LetterboxProto.parseFrom(protoData);
+ mLetterboxPositionForHorizontalReachability =
+ letterboxData.letterboxPositionForHorizontalReachability;
+ mLetterboxPositionForVerticalReachability =
+ letterboxData.letterboxPositionForVerticalReachability;
+ } catch (IOException ioe) {
+ Slog.e(TAG,
+ "Error reading from LetterboxConfigurationPersister. "
+ + "Using default values!", ioe);
+ useDefaultValue();
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ useDefaultValue();
+ Slog.e(TAG, "Error reading from LetterboxConfigurationPersister ", e);
+ }
+ }
+ }
+ }
+
+ private void updateConfiguration() {
+ mPersisterQueue.addItem(new UpdateValuesCommand(mConfigurationFile,
+ mLetterboxPositionForHorizontalReachability,
+ mLetterboxPositionForVerticalReachability,
+ mCompletionCallback), /* flush */ true);
+ }
+
+ private static byte[] readInputStream(InputStream in) throws IOException {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try {
+ byte[] buffer = new byte[1024];
+ int size = in.read(buffer);
+ while (size > 0) {
+ outputStream.write(buffer, 0, size);
+ size = in.read(buffer);
+ }
+ return outputStream.toByteArray();
+ } finally {
+ outputStream.close();
+ }
+ }
+
+ private static class UpdateValuesCommand implements
+ PersisterQueue.WriteQueueItem<UpdateValuesCommand> {
+
+ @NonNull
+ private final AtomicFile mFileToUpdate;
+ @Nullable
+ private final Consumer<String> mOnComplete;
+
+
+ private final int mHorizontalReachability;
+ private final int mVerticalReachability;
+
+ UpdateValuesCommand(@NonNull AtomicFile fileToUpdate,
+ int horizontalReachability, int verticalReachability,
+ @Nullable Consumer<String> onComplete) {
+ mFileToUpdate = fileToUpdate;
+ mHorizontalReachability = horizontalReachability;
+ mVerticalReachability = verticalReachability;
+ mOnComplete = onComplete;
+ }
+
+ @Override
+ public void process() {
+ final WindowManagerProtos.LetterboxProto letterboxData =
+ new WindowManagerProtos.LetterboxProto();
+ letterboxData.letterboxPositionForHorizontalReachability = mHorizontalReachability;
+ letterboxData.letterboxPositionForVerticalReachability = mVerticalReachability;
+ final byte[] bytes = WindowManagerProtos.LetterboxProto.toByteArray(letterboxData);
+
+ FileOutputStream fos = null;
+ try {
+ fos = mFileToUpdate.startWrite();
+ fos.write(bytes);
+ mFileToUpdate.finishWrite(fos);
+ } catch (IOException ioe) {
+ mFileToUpdate.failWrite(fos);
+ Slog.e(TAG,
+ "Error writing to LetterboxConfigurationPersister. "
+ + "Using default values!", ioe);
+ } finally {
+ if (mOnComplete != null) {
+ mOnComplete.accept("UpdateValuesCommand");
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 16f4377..18a7d2e 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -23,12 +23,12 @@
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 8db5289..d34e610 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -323,11 +323,11 @@
mService.closeSurfaceTransaction("RemoteAnimationController#finished");
mIsFinishing = false;
}
+ // Reset input for all activities when the remote animation is finished.
+ final Consumer<ActivityRecord> updateActivities =
+ activity -> activity.setDropInputForAnimation(false);
+ mDisplayContent.forAllActivities(updateActivities);
}
- // Reset input for all activities when the remote animation is finished.
- final Consumer<ActivityRecord> updateActivities =
- activity -> activity.setDropInputForAnimation(false);
- mDisplayContent.forAllActivities(updateActivities);
setRunningRemoteAnimation(false);
ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Finishing remote animation");
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index d8b5d78..0ed4835 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -840,11 +840,8 @@
if (recentsAnimationController != null) {
recentsAnimationController.checkAnimationReady(defaultDisplay.mWallpaperController);
}
- final BackNavigationController backNavigationController =
- mWmService.mAtmService.mBackNavigationController;
- if (backNavigationController != null) {
- backNavigationController.checkAnimationReady(defaultDisplay.mWallpaperController);
- }
+ mWmService.mAtmService.mBackNavigationController
+ .checkAnimationReady(defaultDisplay.mWallpaperController);
for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 9660fe2..b482181 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -69,10 +69,11 @@
import android.view.InputChannel;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.window.ClientWindowFrames;
import android.window.OnBackInvokedCallbackInfo;
@@ -117,7 +118,6 @@
private float mLastReportedAnimatorScale;
private String mPackageName;
private String mRelayoutTag;
- private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities();
private final InsetsSourceControl[] mDummyControls = new InsetsSourceControl[0];
final boolean mSetsUnrestrictedKeepClearAreas;
@@ -196,23 +196,23 @@
@Override
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
+ int viewVisibility, int displayId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
- UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,
+ UserHandle.getUserId(mUid), requestedVisibleTypes, outInputChannel, outInsetsState,
outActiveControls, outAttachedFrame, outSizeCompatScale);
}
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
+ int viewVisibility, int displayId, int userId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
- requestedVisibilities, outInputChannel, outInsetsState, outActiveControls,
+ requestedVisibleTypes, outInputChannel, outInsetsState, outActiveControls,
outAttachedFrame, outSizeCompatScale);
}
@@ -221,8 +221,9 @@
int viewVisibility, int displayId, InsetsState outInsetsState, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
- UserHandle.getUserId(mUid), mDummyRequestedVisibilities, null /* outInputChannel */,
- outInsetsState, mDummyControls, outAttachedFrame, outSizeCompatScale);
+ UserHandle.getUserId(mUid), WindowInsets.Type.defaultVisible(),
+ null /* outInputChannel */, outInsetsState, mDummyControls, outAttachedFrame,
+ outSizeCompatScale);
}
@Override
@@ -683,12 +684,12 @@
}
@Override
- public void updateRequestedVisibilities(IWindow window, InsetsVisibilities visibilities) {
+ public void updateRequestedVisibleTypes(IWindow window, @InsetsType int requestedVisibleTypes) {
synchronized (mService.mGlobalLock) {
final WindowState windowState = mService.windowForClientLocked(this, window,
false /* throwOnError */);
if (windowState != null) {
- windowState.setRequestedVisibilities(visibilities);
+ windowState.setRequestedVisibleTypes(requestedVisibleTypes);
windowState.getDisplayContent().getInsetsPolicy().onInsetsModified(windowState);
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 391d081..435ab97 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -171,8 +171,6 @@
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.InsetsState;
@@ -197,6 +195,8 @@
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.AppTimeTracker;
@@ -3513,7 +3513,7 @@
final WindowState topMainWin = getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION);
if (topMainWin != null) {
info.mainWindowLayoutParams = topMainWin.getAttrs();
- info.requestedVisibilities.set(topMainWin.getRequestedVisibilities());
+ info.requestedVisibleTypes = topMainWin.getRequestedVisibleTypes();
}
}
// If the developer has persist a different configuration, we need to override it to the
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 867833a..509b1e6 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -184,19 +184,30 @@
}
void dispose() {
- while (!mOrganizedTaskFragments.isEmpty()) {
- final TaskFragment taskFragment = mOrganizedTaskFragments.get(0);
- // Cleanup before remove to prevent it from sending any additional event, such as
- // #onTaskFragmentVanished, to the removed organizer.
+ for (int i = mOrganizedTaskFragments.size() - 1; i >= 0; i--) {
+ // Cleanup the TaskFragmentOrganizer from all TaskFragments it organized before
+ // removing the windows to prevent it from adding any additional TaskFragment
+ // pending event.
+ final TaskFragment taskFragment = mOrganizedTaskFragments.get(i);
taskFragment.onTaskFragmentOrganizerRemoved();
- taskFragment.removeImmediately();
- mOrganizedTaskFragments.remove(taskFragment);
}
+
+ // Defer to avoid unnecessary layout when there are multiple TaskFragments removal.
+ mAtmService.deferWindowLayout();
+ try {
+ while (!mOrganizedTaskFragments.isEmpty()) {
+ final TaskFragment taskFragment = mOrganizedTaskFragments.remove(0);
+ taskFragment.removeImmediately();
+ }
+ } finally {
+ mAtmService.continueWindowLayout();
+ }
+
for (int i = mDeferredTransitions.size() - 1; i >= 0; i--) {
// Cleanup any running transaction to unblock the current transition.
onTransactionFinished(mDeferredTransitions.keyAt(i));
}
- mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/);
+ mOrganizer.asBinder().unlinkToDeath(this, 0 /* flags */);
}
@NonNull
@@ -426,7 +437,6 @@
@Override
public void unregisterOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
- validateAndGetState(organizer);
final int pid = Binder.getCallingPid();
final long uid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
@@ -607,6 +617,13 @@
int opType, @NonNull Throwable exception) {
validateAndGetState(organizer);
Slog.w(TAG, "onTaskFragmentError ", exception);
+ final PendingTaskFragmentEvent vanishedEvent = taskFragment != null
+ ? getPendingTaskFragmentEvent(taskFragment, PendingTaskFragmentEvent.EVENT_VANISHED)
+ : null;
+ if (vanishedEvent != null) {
+ // No need to notify if the TaskFragment has been removed.
+ return;
+ }
addPendingEvent(new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_ERROR, organizer)
.setErrorCallbackToken(errorCallbackToken)
@@ -690,11 +707,17 @@
}
private void removeOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
- final TaskFragmentOrganizerState state = validateAndGetState(organizer);
+ final TaskFragmentOrganizerState state = mTaskFragmentOrganizerState.get(
+ organizer.asBinder());
+ if (state == null) {
+ Slog.w(TAG, "The organizer has already been removed.");
+ return;
+ }
+ // Remove any pending event of this organizer first because state.dispose() may trigger
+ // event dispatch as result of surface placement.
+ mPendingTaskFragmentEvents.remove(organizer.asBinder());
// remove all of the children of the organized TaskFragment
state.dispose();
- // Remove any pending event of this organizer.
- mPendingTaskFragmentEvents.remove(organizer.asBinder());
mTaskFragmentOrganizerState.remove(organizer.asBinder());
}
@@ -878,23 +901,6 @@
return null;
}
- private boolean shouldSendEventWhenTaskInvisible(@NonNull PendingTaskFragmentEvent event) {
- if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR
- // Always send parent info changed to update task visibility
- || event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
- return true;
- }
-
- final TaskFragmentOrganizerState state =
- mTaskFragmentOrganizerState.get(event.mTaskFragmentOrg.asBinder());
- final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos.get(event.mTaskFragment);
- final TaskFragmentInfo info = event.mTaskFragment.getTaskFragmentInfo();
- // Send an info changed callback if this event is for the last activities to finish in a
- // TaskFragment so that the {@link TaskFragmentOrganizer} can delete this TaskFragment.
- return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
- && lastInfo != null && lastInfo.hasRunningActivity() && info.isEmpty();
- }
-
void dispatchPendingEvents() {
if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
|| mPendingTaskFragmentEvents.isEmpty()) {
@@ -908,37 +914,19 @@
}
}
- void dispatchPendingEvents(@NonNull TaskFragmentOrganizerState state,
+ private void dispatchPendingEvents(@NonNull TaskFragmentOrganizerState state,
@NonNull List<PendingTaskFragmentEvent> pendingEvents) {
if (pendingEvents.isEmpty()) {
return;
}
-
- final ArrayList<Task> visibleTasks = new ArrayList<>();
- final ArrayList<Task> invisibleTasks = new ArrayList<>();
- final ArrayList<PendingTaskFragmentEvent> candidateEvents = new ArrayList<>();
- for (int i = 0, n = pendingEvents.size(); i < n; i++) {
- final PendingTaskFragmentEvent event = pendingEvents.get(i);
- final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null;
- // TODO(b/251132298): move visibility check to the client side.
- if (task != null && (task.lastActiveTime <= event.mDeferTime
- || !(isTaskVisible(task, visibleTasks, invisibleTasks)
- || shouldSendEventWhenTaskInvisible(event)))) {
- // Defer sending events to the TaskFragment until the host task is active again.
- event.mDeferTime = task.lastActiveTime;
- continue;
- }
- candidateEvents.add(event);
- }
- final int numEvents = candidateEvents.size();
- if (numEvents == 0) {
+ if (shouldDeferPendingEvents(state, pendingEvents)) {
return;
}
-
mTmpTaskSet.clear();
+ final int numEvents = pendingEvents.size();
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
for (int i = 0; i < numEvents; i++) {
- final PendingTaskFragmentEvent event = candidateEvents.get(i);
+ final PendingTaskFragmentEvent event = pendingEvents.get(i);
if (event.mEventType == PendingTaskFragmentEvent.EVENT_APPEARED
|| event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED) {
final Task task = event.mTaskFragment.getTask();
@@ -954,7 +942,47 @@
}
mTmpTaskSet.clear();
state.dispatchTransaction(transaction);
- pendingEvents.removeAll(candidateEvents);
+ pendingEvents.clear();
+ }
+
+ /**
+ * Whether or not to defer sending the events to the organizer to avoid waking the app process
+ * when it is in background. We want to either send all events or none to avoid inconsistency.
+ */
+ private boolean shouldDeferPendingEvents(@NonNull TaskFragmentOrganizerState state,
+ @NonNull List<PendingTaskFragmentEvent> pendingEvents) {
+ final ArrayList<Task> visibleTasks = new ArrayList<>();
+ final ArrayList<Task> invisibleTasks = new ArrayList<>();
+ for (int i = 0, n = pendingEvents.size(); i < n; i++) {
+ final PendingTaskFragmentEvent event = pendingEvents.get(i);
+ if (event.mEventType != PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED
+ && event.mEventType != PendingTaskFragmentEvent.EVENT_INFO_CHANGED
+ && event.mEventType != PendingTaskFragmentEvent.EVENT_APPEARED) {
+ // Send events for any other types.
+ return false;
+ }
+
+ // Check if we should send the event given the Task visibility and events.
+ final Task task;
+ if (event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
+ task = event.mTask;
+ } else {
+ task = event.mTaskFragment.getTask();
+ }
+ if (task.lastActiveTime > event.mDeferTime
+ && isTaskVisible(task, visibleTasks, invisibleTasks)) {
+ // Send events when the app has at least one visible Task.
+ return false;
+ } else if (shouldSendEventWhenTaskInvisible(task, state, event)) {
+ // Sent events even if the Task is invisible.
+ return false;
+ }
+
+ // Defer sending events to the organizer until the host task is active (visible) again.
+ event.mDeferTime = task.lastActiveTime;
+ }
+ // Defer for invisible Task.
+ return true;
}
private static boolean isTaskVisible(@NonNull Task task,
@@ -975,6 +1003,28 @@
}
}
+ private boolean shouldSendEventWhenTaskInvisible(@NonNull Task task,
+ @NonNull TaskFragmentOrganizerState state,
+ @NonNull PendingTaskFragmentEvent event) {
+ final TaskFragmentParentInfo lastParentInfo = state.mLastSentTaskFragmentParentInfos
+ .get(task.mTaskId);
+ if (lastParentInfo == null || lastParentInfo.isVisible()) {
+ // When the Task was visible, or when there was no Task info changed sent (in which case
+ // the organizer will consider it as visible by default), always send the event to
+ // update the Task visibility.
+ return true;
+ }
+ if (event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED) {
+ // Send info changed if the TaskFragment is becoming empty/non-empty so the
+ // organizer can choose whether or not to remove the TaskFragment.
+ final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos
+ .get(event.mTaskFragment);
+ final boolean isEmpty = event.mTaskFragment.getNonFinishingActivityCount() == 0;
+ return lastInfo == null || lastInfo.isEmpty() != isEmpty;
+ }
+ return false;
+ }
+
void dispatchPendingInfoChangedEvent(@NonNull TaskFragment taskFragment) {
final PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment,
PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java
index 09fd900..29c192c 100644
--- a/services/core/java/com/android/server/wm/TaskPersister.java
+++ b/services/core/java/com/android/server/wm/TaskPersister.java
@@ -30,12 +30,12 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 9306749..29c98b9 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -587,7 +587,7 @@
final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(),
- mHighResTaskSnapshotScale, insetsState);
+ mHighResTaskSnapshotScale, mainWindow.getRequestedVisibleTypes());
final int taskWidth = taskBounds.width();
final int taskHeight = taskBounds.height();
final int width = (int) (taskWidth * mHighResTaskSnapshotScale);
@@ -750,12 +750,12 @@
private final int mWindowFlags;
private final int mWindowPrivateFlags;
private final float mScale;
- private final InsetsState mInsetsState;
+ private final @Type.InsetsType int mRequestedVisibleTypes;
private final Rect mSystemBarInsets = new Rect();
SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
ActivityManager.TaskDescription taskDescription, float scale,
- InsetsState insetsState) {
+ @Type.InsetsType int requestedVisibleTypes) {
mWindowFlags = windowFlags;
mWindowPrivateFlags = windowPrivateFlags;
mScale = scale;
@@ -774,7 +774,7 @@
&& context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
mStatusBarPaint.setColor(mStatusBarColor);
mNavigationBarPaint.setColor(mNavigationBarColor);
- mInsetsState = insetsState;
+ mRequestedVisibleTypes = requestedVisibleTypes;
}
void setInsets(Rect systemBarInsets) {
@@ -785,7 +785,7 @@
final boolean forceBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) {
+ mRequestedVisibleTypes, mStatusBarColor, mWindowFlags, forceBarBackground)) {
return (int) (mSystemBarInsets.top * mScale);
} else {
return 0;
@@ -796,7 +796,7 @@
final boolean forceBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground);
+ mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags, forceBarBackground);
}
void drawDecors(Canvas c) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b2c8b7a..80c9803 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1568,7 +1568,11 @@
change.setMode(info.getTransitMode(target));
change.setStartAbsBounds(info.mAbsoluteBounds);
change.setFlags(info.getChangeFlags(target));
+
final Task task = target.asTask();
+ final TaskFragment taskFragment = target.asTaskFragment();
+ final ActivityRecord activityRecord = target.asActivityRecord();
+
if (task != null) {
final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
task.fillTaskInfo(tinfo);
@@ -1602,12 +1606,7 @@
change.setEndRelOffset(bounds.left - parentBounds.left,
bounds.top - parentBounds.top);
int endRotation = target.getWindowConfiguration().getRotation();
- final ActivityRecord activityRecord = target.asActivityRecord();
if (activityRecord != null) {
- final Task arTask = activityRecord.getTask();
- final int backgroundColor = ColorUtils.setAlphaComponent(
- arTask.getTaskDescription().getBackgroundColor(), 255);
- change.setBackgroundColor(backgroundColor);
// TODO(b/227427984): Shell needs to aware letterbox.
// Always use parent bounds of activity because letterbox area (e.g. fixed aspect
// ratio or size compat mode) should be included in the animation.
@@ -1620,6 +1619,18 @@
} else {
change.setEndAbsBounds(bounds);
}
+
+ if (activityRecord != null || (taskFragment != null && taskFragment.isEmbedded())) {
+ // Set background color to Task theme color for activity and embedded TaskFragment
+ // in case we want to show background during the animation.
+ final Task parentTask = activityRecord != null
+ ? activityRecord.getTask()
+ : taskFragment.getTask();
+ final int backgroundColor = ColorUtils.setAlphaComponent(
+ parentTask.getTaskDescription().getBackgroundColor(), 255);
+ change.setBackgroundColor(backgroundColor);
+ }
+
change.setRotation(info.mRotation, endRotation);
if (info.mSnapshot != null) {
change.setSnapshot(info.mSnapshot, info.mSnapshotLuma);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 81d6795..6522d93 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -239,8 +239,7 @@
private boolean isBackNavigationTarget(WindowState w) {
// The window is in animating by back navigation and set to show wallpaper.
- final BackNavigationController controller = mService.mAtmService.mBackNavigationController;
- return controller != null && controller.isWallpaperVisible(w);
+ return mService.mAtmService.mBackNavigationController.isWallpaperVisible(w);
}
/**
@@ -831,9 +830,7 @@
// If there was a pending back navigation animation that would show wallpaper, start
// the animation due to it was skipped in previous surface placement.
- if (mService.mAtmService.mBackNavigationController != null) {
- mService.mAtmService.mBackNavigationController.startAnimation();
- }
+ mService.mAtmService.mBackNavigationController.startAnimation();
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index c4c66d8..0b5de85 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -41,6 +41,7 @@
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
import static com.android.server.wm.AppTransition.isActivityTransitOld;
+import static com.android.server.wm.AppTransition.isTaskFragmentTransitOld;
import static com.android.server.wm.AppTransition.isTaskTransitOld;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
@@ -2999,10 +3000,17 @@
// {@link Activity#overridePendingTransition(int, int, int)}.
@ColorInt int backdropColor = 0;
if (controller.isFromActivityEmbedding()) {
- final int animAttr = AppTransition.mapOpenCloseTransitTypes(transit, enter);
- final Animation a = animAttr != 0
- ? appTransition.loadAnimationAttr(lp, animAttr, transit) : null;
- showBackdrop = a != null && a.getShowBackdrop();
+ if (isChanging) {
+ // When there are more than one changing containers, it may leave part of the
+ // screen empty. Show background color to cover that.
+ showBackdrop = getDisplayContent().mChangingContainers.size() > 1;
+ } else {
+ // Check whether or not to show backdrop for open/close transition.
+ final int animAttr = AppTransition.mapOpenCloseTransitTypes(transit, enter);
+ final Animation a = animAttr != 0
+ ? appTransition.loadAnimationAttr(lp, animAttr, transit) : null;
+ showBackdrop = a != null && a.getShowBackdrop();
+ }
backdropColor = appTransition.getNextAppTransitionBackgroundColor();
}
final Rect localBounds = new Rect(mTmpRect);
@@ -3105,9 +3113,16 @@
}
}
+ // Check if the animation requests to show background color for Activity and embedded
+ // TaskFragment.
final ActivityRecord activityRecord = asActivityRecord();
- if (activityRecord != null && isActivityTransitOld(transit)
- && adapter.getShowBackground()) {
+ final TaskFragment taskFragment = asTaskFragment();
+ if (adapter.getShowBackground()
+ // Check if it is Activity transition.
+ && ((activityRecord != null && isActivityTransitOld(transit))
+ // Check if it is embedded TaskFragment transition.
+ || (taskFragment != null && taskFragment.isEmbedded()
+ && isTaskFragmentTransitOld(transit)))) {
final @ColorInt int backgroundColorForTransition;
if (adapter.getBackgroundColor() != 0) {
// If available use the background color provided through getBackgroundColor
@@ -3117,9 +3132,11 @@
// Otherwise default to the window's background color if provided through
// the theme as the background color for the animation - the top most window
// with a valid background color and showBackground set takes precedence.
- final Task arTask = activityRecord.getTask();
+ final Task parentTask = activityRecord != null
+ ? activityRecord.getTask()
+ : taskFragment.getTask();
backgroundColorForTransition = ColorUtils.setAlphaComponent(
- arTask.getTaskDescription().getBackgroundColor(), 255);
+ parentTask.getTaskDescription().getBackgroundColor(), 255);
}
animationRunnerBuilder.setTaskBackgroundColor(backgroundColorForTransition);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c9d3dac..848c231 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -266,9 +266,9 @@
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputWindowHandle;
+import android.view.InsetsFrameProvider;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
@@ -283,6 +283,7 @@
import android.view.View;
import android.view.WindowContentFrameStats;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManager.LayoutParams;
@@ -1409,7 +1410,7 @@
}
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
- int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
+ int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
@@ -1635,7 +1636,7 @@
attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
attrs.inputFeatures = sanitizeSpyWindow(attrs.inputFeatures, win.getName(), callingUid,
callingPid);
- win.setRequestedVisibilities(requestedVisibilities);
+ win.setRequestedVisibleTypes(requestedVisibleTypes);
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
if (res != ADD_OKAY) {
@@ -2277,6 +2278,27 @@
"Insets types can not be changed after the window is "
+ "added.");
}
+ final InsetsFrameProvider.InsetsSizeOverride[] overrides =
+ win.mAttrs.providedInsets[i].insetsSizeOverrides;
+ final InsetsFrameProvider.InsetsSizeOverride[] newOverrides =
+ attrs.providedInsets[i].insetsSizeOverrides;
+ if (!(overrides == null && newOverrides == null)) {
+ if (overrides == null || newOverrides == null
+ || (overrides.length != newOverrides.length)) {
+ throw new IllegalArgumentException(
+ "Insets override types can not be changed after the "
+ + "window is added.");
+ } else {
+ final int overrideTypes = overrides.length;
+ for (int j = 0; j < overrideTypes; j++) {
+ if (overrides[j].windowType != newOverrides[j].windowType) {
+ throw new IllegalArgumentException(
+ "Insets override types can not be changed after"
+ + " the window is added.");
+ }
+ }
+ }
+ }
}
}
}
@@ -4428,7 +4450,8 @@
}
@Override
- public void updateDisplayWindowRequestedVisibilities(int displayId, InsetsVisibilities vis) {
+ public void updateDisplayWindowRequestedVisibleTypes(
+ int displayId, @InsetsType int requestedVisibleTypes) {
if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
@@ -4440,7 +4463,7 @@
if (dc == null || dc.mRemoteInsetsControlTarget == null) {
return;
}
- dc.mRemoteInsetsControlTarget.setRequestedVisibilities(vis);
+ dc.mRemoteInsetsControlTarget.setRequestedVisibleTypes(requestedVisibleTypes);
dc.getInsetsStateController().onInsetsModified(dc.mRemoteInsetsControlTarget);
}
} finally {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 36389ea..6d750a4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -30,7 +30,6 @@
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_INVALID;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.SurfaceControl.Transaction;
import static android.view.SurfaceControl.getGlobalTransaction;
import static android.view.ViewRootImpl.LOCAL_LAYOUT;
@@ -41,6 +40,7 @@
import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
+import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
@@ -233,7 +233,6 @@
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.Surface.Rotation;
import android.view.SurfaceControl;
@@ -767,7 +766,7 @@
*/
private boolean mIsDimming = false;
- private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
/**
* Freeze the insets state in some cases that not necessarily keeps up-to-date to the client.
@@ -833,31 +832,33 @@
*/
private int mSurfaceTranslationY;
+ @Override
+ public boolean isRequestedVisible(@InsetsType int types) {
+ return (mRequestedVisibleTypes & types) != 0;
+ }
+
/**
- * Returns the visibility of the given {@link InternalInsetsType type} requested by the client.
+ * Returns requested visible types of insets.
*
- * @param type the given {@link InternalInsetsType type}.
- * @return {@code true} if the type is requested visible.
+ * @return an integer as the requested visible insets types.
*/
@Override
- public boolean getRequestedVisibility(@InternalInsetsType int type) {
- return mRequestedVisibilities.getVisibility(type);
+ public @InsetsType int getRequestedVisibleTypes() {
+ return mRequestedVisibleTypes;
}
/**
- * Returns all the requested visibilities.
- *
- * @return an {@link InsetsVisibilities} as the requested visibilities.
+ * @see #getRequestedVisibleTypes()
*/
- InsetsVisibilities getRequestedVisibilities() {
- return mRequestedVisibilities;
+ void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
+ if (mRequestedVisibleTypes != requestedVisibleTypes) {
+ mRequestedVisibleTypes = requestedVisibleTypes;
+ }
}
- /**
- * @see #getRequestedVisibility(int)
- */
- void setRequestedVisibilities(InsetsVisibilities visibilities) {
- mRequestedVisibilities.set(visibilities);
+ @VisibleForTesting
+ void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes, @InsetsType int mask) {
+ setRequestedVisibleTypes(mRequestedVisibleTypes & ~mask | requestedVisibleTypes & mask);
}
/**
@@ -973,7 +974,7 @@
boolean isImplicitlyExcludingAllSystemGestures() {
final boolean stickyHideNav =
mAttrs.insetsFlags.behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
- && !getRequestedVisibility(ITYPE_NAVIGATION_BAR);
+ && !isRequestedVisible(navigationBars());
return stickyHideNav && mWmService.mConstants.mSystemGestureExcludedByPreQStickyImmersive
&& mActivityRecord != null && mActivityRecord.mTargetSdk < Build.VERSION_CODES.Q;
}
@@ -1718,7 +1719,7 @@
InsetsState getInsetsStateWithVisibilityOverride() {
final InsetsState state = new InsetsState(getInsetsState());
for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
- final boolean requestedVisible = getRequestedVisibility(type);
+ final boolean requestedVisible = isRequestedVisible(InsetsState.toPublicType(type));
InsetsSource source = state.peekSource(type);
if (source != null && source.isVisible() != requestedVisible) {
source = new InsetsSource(source);
@@ -4436,9 +4437,10 @@
pw.println(prefix + "keepClearAreas: restricted=" + mKeepClearAreas
+ ", unrestricted=" + mUnrestrictedKeepClearAreas);
if (dumpAll) {
- final String visibilityString = mRequestedVisibilities.toString();
- if (!visibilityString.isEmpty()) {
- pw.println(prefix + "Requested visibilities: " + visibilityString);
+ if (mRequestedVisibleTypes != WindowInsets.Type.defaultVisible()) {
+ pw.println(prefix + "Requested non-default-visibility types: "
+ + WindowInsets.Type.toString(
+ mRequestedVisibleTypes ^ WindowInsets.Type.defaultVisible()));
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 222a96d..8047a53 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -47,11 +47,11 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.UserRestrictionsUtils;
import com.android.server.utils.Slogf;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
index cc32c4d..953a9ee 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
@@ -27,10 +27,11 @@
import android.os.Environment;
import android.util.AtomicFile;
import android.util.Log;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 0305c35..8e430b3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -29,12 +29,12 @@
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.utils.Slogf;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a561307..70422bb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -332,8 +332,6 @@
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.IWindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -363,6 +361,8 @@
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.PasswordValidationError;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.net.module.util.ProxyUtils;
import com.android.server.AlarmManagerInternal;
import com.android.server.LocalServices;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 2ab5464..3040af2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -27,11 +27,11 @@
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java
index 289ed36..035f762 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java
@@ -24,12 +24,12 @@
import android.text.TextUtils;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
index ad652df..65b99c5 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
@@ -20,9 +20,9 @@
import android.os.UserHandle
import android.util.ArrayMap
import android.util.SparseArray
-import android.util.TypedXmlPullParser
-import android.util.TypedXmlSerializer
import android.util.Xml
+import com.android.modules.utils.TypedXmlPullParser
+import com.android.modules.utils.TypedXmlSerializer
import com.android.server.pm.verify.domain.DomainVerificationPersistence
import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index b7e66f2..5b7b8f4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -26,11 +26,14 @@
import static com.android.server.am.BroadcastQueueTest.getUidForPackage;
import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
import android.annotation.NonNull;
@@ -41,6 +44,7 @@
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Bundle;
+import android.os.BundleMerger;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.provider.Settings;
@@ -55,12 +59,15 @@
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(MockitoJUnitRunner.class)
public class BroadcastQueueModernImplTest {
private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
+ private static final int TEST_UID2 = android.os.Process.FIRST_APPLICATION_UID + 1;
@Mock ActivityManagerService mAms;
@Mock ProcessRecord mProcess;
@@ -85,6 +92,10 @@
mHandlerThread.start();
mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
+ mConstants.DELAY_URGENT_MILLIS = -120_000;
+ mConstants.DELAY_NORMAL_MILLIS = 10_000;
+ mConstants.DELAY_CACHED_MILLIS = 120_000;
+
mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
mConstants, mConstants);
@@ -278,7 +289,7 @@
final long notCachedRunnableAt = queue.getRunnableAt();
queue.setProcessCached(true);
final long cachedRunnableAt = queue.getRunnableAt();
- assertTrue(cachedRunnableAt > notCachedRunnableAt);
+ assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt);
assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked());
}
@@ -306,13 +317,13 @@
// (b) the next one up is the fg-priority broadcast despite its later enqueue time
queue.setProcessCached(false);
assertTrue(queue.isRunnable());
- assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
+ assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime);
assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord);
queue.setProcessCached(true);
assertTrue(queue.isRunnable());
- assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
+ assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime);
assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord);
}
@@ -356,12 +367,12 @@
mConstants.MAX_PENDING_BROADCASTS = 128;
queue.invalidateRunnableAt();
- assertTrue(queue.getRunnableAt() > airplaneRecord.enqueueTime);
+ assertThat(queue.getRunnableAt()).isGreaterThan(airplaneRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
mConstants.MAX_PENDING_BROADCASTS = 1;
queue.invalidateRunnableAt();
- assertTrue(queue.getRunnableAt() == airplaneRecord.enqueueTime);
+ assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_MAX_PENDING, queue.getRunnableAtReason());
}
@@ -465,6 +476,62 @@
List.of(musicVolumeChanged, alarmVolumeChanged, timeTick));
}
+ /**
+ * Verify that sending a broadcast with DELIVERY_GROUP_POLICY_MERGED works as expected.
+ */
+ @Test
+ public void testDeliveryGroupPolicy_merged() {
+ final BundleMerger extrasMerger = new BundleMerger();
+ extrasMerger.setMergeStrategy(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+ BundleMerger.STRATEGY_ARRAY_APPEND);
+
+ final Intent packageChangedForUid = createPackageChangedIntent(TEST_UID,
+ List.of("com.testuid.component1"));
+ final BroadcastOptions optionsPackageChangedForUid = BroadcastOptions.makeBasic();
+ optionsPackageChangedForUid.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
+ optionsPackageChangedForUid.setDeliveryGroupKey("package", String.valueOf(TEST_UID));
+ optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
+
+ final Intent secondPackageChangedForUid = createPackageChangedIntent(TEST_UID,
+ List.of("com.testuid.component2", "com.testuid.component3"));
+
+ final Intent packageChangedForUid2 = createPackageChangedIntent(TEST_UID2,
+ List.of("com.testuid2.component1"));
+ final BroadcastOptions optionsPackageChangedForUid2 = BroadcastOptions.makeBasic();
+ optionsPackageChangedForUid.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
+ optionsPackageChangedForUid.setDeliveryGroupKey("package", String.valueOf(TEST_UID2));
+ optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
+
+ // Halt all processing so that we get a consistent view
+ mHandlerThread.getLooper().getQueue().postSyncBarrier();
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid,
+ optionsPackageChangedForUid));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid2,
+ optionsPackageChangedForUid2));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(secondPackageChangedForUid,
+ optionsPackageChangedForUid));
+
+ final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final Intent expectedPackageChangedForUid = createPackageChangedIntent(TEST_UID,
+ List.of("com.testuid.component2", "com.testuid.component3",
+ "com.testuid.component1"));
+ // Verify that packageChangedForUid and secondPackageChangedForUid broadcasts
+ // have been merged.
+ verifyPendingRecords(queue, List.of(packageChangedForUid2, expectedPackageChangedForUid));
+ }
+
+ private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
+ final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ packageChangedIntent.putExtra(Intent.EXTRA_UID, uid);
+ packageChangedIntent.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+ componentNameList.toArray());
+ return packageChangedIntent;
+ }
+
private void verifyPendingRecords(BroadcastProcessQueue queue,
List<Intent> intents) {
for (int i = 0; i < intents.size(); i++) {
@@ -475,9 +542,45 @@
+ ", actual_extras=" + actualIntent.getExtras()
+ ", expected_extras=" + expectedIntent.getExtras();
assertTrue(errMsg, actualIntent.filterEquals(expectedIntent));
- assertTrue(errMsg, Bundle.kindofEquals(
- actualIntent.getExtras(), expectedIntent.getExtras()));
+ assertBundleEquals(expectedIntent.getExtras(), actualIntent.getExtras());
}
assertTrue(queue.isEmpty());
}
+
+ private void assertBundleEquals(Bundle expected, Bundle actual) {
+ final String errMsg = "expected=" + expected + ", actual=" + actual;
+ if (expected == actual) {
+ return;
+ } else if (expected == null || actual == null) {
+ fail(errMsg);
+ }
+ if (!expected.keySet().equals(actual.keySet())) {
+ fail(errMsg);
+ }
+ for (String key : expected.keySet()) {
+ final Object expectedValue = expected.get(key);
+ final Object actualValue = actual.get(key);
+ if (expectedValue == actualValue) {
+ continue;
+ } else if (expectedValue == null || actualValue == null) {
+ fail(errMsg);
+ }
+ assertEquals(errMsg, expectedValue.getClass(), actualValue.getClass());
+ if (expectedValue.getClass().isArray()) {
+ assertEquals(errMsg, Array.getLength(expectedValue), Array.getLength(actualValue));
+ for (int i = 0; i < Array.getLength(expectedValue); ++i) {
+ assertEquals(errMsg, Array.get(expectedValue, i), Array.get(actualValue, i));
+ }
+ } else if (expectedValue instanceof ArrayList) {
+ final ArrayList<?> expectedList = (ArrayList<?>) expectedValue;
+ final ArrayList<?> actualList = (ArrayList<?>) actualValue;
+ assertEquals(errMsg, expectedList.size(), actualList.size());
+ for (int i = 0; i < expectedList.size(); ++i) {
+ assertEquals(errMsg, expectedList.get(i), actualList.get(i));
+ }
+ } else {
+ assertEquals(errMsg, expectedValue, actualValue);
+ }
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index d9a26c6..e1a4c1d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -888,7 +888,7 @@
}) {
// Confirm expected OOM adjustments; we were invoked once to upgrade
// and once to downgrade
- assertEquals(ActivityManager.PROCESS_STATE_RECEIVER,
+ assertEquals(String.valueOf(receiverApp), ActivityManager.PROCESS_STATE_RECEIVER,
receiverApp.mState.getReportedProcState());
verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp));
@@ -897,8 +897,8 @@
// cold-started apps to be thawed, but the modern stack does
} else {
// Confirm that app was thawed
- verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(receiverApp),
- eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
+ verify(mAms.mOomAdjuster.mCachedAppOptimizer, atLeastOnce()).unfreezeTemporarily(
+ eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
// Confirm that we added package to process
verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
@@ -1599,4 +1599,39 @@
assertTrue(mQueue.isBeyondBarrierLocked(afterFirst));
assertTrue(mQueue.isBeyondBarrierLocked(afterSecond));
}
+
+ /**
+ * Verify that we OOM adjust for manifest receivers.
+ */
+ @Test
+ public void testOomAdjust_Manifest() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_RED))));
+
+ waitForIdle();
+ verify(mAms, atLeastOnce()).enqueueOomAdjTargetLocked(any());
+ }
+
+ /**
+ * Verify that we never OOM adjust for registered receivers.
+ */
+ @Test
+ public void testOomAdjust_Registered() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverApp),
+ makeRegisteredReceiver(receiverApp),
+ makeRegisteredReceiver(receiverApp))));
+
+ waitForIdle();
+ verify(mAms, never()).enqueueOomAdjTargetLocked(any());
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index 7111047..e08a715 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -36,13 +36,14 @@
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index e28d331..28c78b2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -22,6 +22,7 @@
import android.content.pm.VersionedPackage
import android.os.Build
import android.os.storage.StorageManager
+import android.os.UserHandle
import android.util.ArrayMap
import android.util.PackageUtils
import com.android.server.SystemConfig.SharedLibraryEntry
@@ -222,10 +223,11 @@
val parsedPackage = pair.second as ParsedPackage
val scanRequest = ScanRequest(parsedPackage, null, null, null, null,
null, null, null, 0, 0, false, null, null)
- val scanResult = ScanResult(scanRequest, true, null, null, false, 0, null, null, null)
+ val scanResult = ScanResult(scanRequest, null, null, false, 0, null, null, null)
+ var installRequest = InstallRequest(parsedPackage, 0, 0, UserHandle(0), scanResult)
val latestInfoSetting =
- mSharedLibrariesImpl.getStaticSharedLibLatestVersionSetting(scanResult)!!
+ mSharedLibrariesImpl.getStaticSharedLibLatestVersionSetting(installRequest)!!
assertThat(latestInfoSetting).isNotNull()
assertThat(latestInfoSetting.packageName).isEqualTo(STATIC_LIB_PACKAGE_NAME)
@@ -305,10 +307,11 @@
@Test
fun getAllowedSharedLibInfos_withStaticSharedLibInfo() {
val testInfo = libOfStatic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME, 1L)
- val scanResult = ScanResult(mock(), true, null, null,
+ val scanResult = ScanResult(mock(), null, null,
false, 0, null, testInfo, null)
+ var installRequest = InstallRequest(mock(), 0, 0, UserHandle(0), scanResult)
- val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(scanResult)
+ val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(installRequest)
assertThat(allowedInfos).hasSize(1)
assertThat(allowedInfos[0].name).isEqualTo(TEST_LIB_NAME)
@@ -327,10 +330,11 @@
.setPkgFlags(ApplicationInfo.FLAG_SYSTEM).build()
val scanRequest = ScanRequest(parsedPackage, null, null, null, null,
null, null, null, 0, 0, false, null, null)
- val scanResult = ScanResult(scanRequest, true, packageSetting, null,
+ val scanResult = ScanResult(scanRequest, packageSetting, null,
false, 0, null, null, listOf(testInfo))
+ var installRequest = InstallRequest(parsedPackage, 0, 0, UserHandle(0), scanResult)
- val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(scanResult)
+ val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(installRequest)
assertThat(allowedInfos).hasSize(1)
assertThat(allowedInfos[0].name).isEqualTo(TEST_LIB_NAME)
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 5fb3a4e..7fd1ddb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -69,8 +69,6 @@
import android.testing.TestableContext;
import android.util.Log;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.Display;
@@ -80,6 +78,8 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.R;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.wallpaper.WallpaperManagerService.WallpaperData;
import com.android.server.wm.WindowManagerInternal;
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 0b776a3..fe92a1d 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -24,6 +24,7 @@
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -246,7 +247,7 @@
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
- mUserController.startUser(TEST_USER_ID, true /* foreground */);
+ mUserController.startUser(TEST_USER_ID, /* foreground= */ true);
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
@@ -258,6 +259,8 @@
assertFalse(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ true));
// Make sure no intents have been fired for pre-created users.
assertTrue(mInjector.mSentIntents.isEmpty());
+
+ verifyUserNeverAssignedToDisplay();
}
@Test
@@ -280,6 +283,8 @@
// binder calls, but their side effects (in this case, that the user is stopped right away)
assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
.containsExactly(USER_START_MSG);
+
+ verifyUserAssignedToDisplay(TEST_PRE_CREATED_USER_ID, Display.DEFAULT_DISPLAY);
}
private void startUserAssertions(
@@ -303,6 +308,7 @@
assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state);
assertEquals("Unexpected old user id", 0, reportMsg.arg1);
assertEquals("Unexpected new user id", TEST_USER_ID, reportMsg.arg2);
+ verifyUserAssignedToDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY);
}
@Test
@@ -313,6 +319,8 @@
mUserController.startUserInForeground(NONEXIST_USER_ID);
verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
verify(mInjector.getWindowManager()).setSwitchingUser(false);
+
+ verifyUserNeverAssignedToDisplay();
}
@Test
@@ -395,6 +403,7 @@
verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(TEST_USER_ID, false);
+ verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
}
@Test
@@ -403,7 +412,7 @@
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
// Start user -- this will update state of mUserController
- mUserController.startUser(TEST_USER_ID, true);
+ mUserController.startUser(TEST_USER_ID, /* foreground=*/ true);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
assertNotNull(reportMsg);
UserState userState = (UserState) reportMsg.obj;
@@ -415,6 +424,7 @@
verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(TEST_USER_ID, false);
+ verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
}
@Test
@@ -423,7 +433,7 @@
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
// Start user -- this will update state of mUserController
- mUserController.startUser(TEST_USER_ID, true);
+ mUserController.startUser(TEST_USER_ID, /* foreground=*/ true);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
assertNotNull(reportMsg);
UserState userState = (UserState) reportMsg.obj;
@@ -521,6 +531,7 @@
assertFalse(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}),
mUserController.getRunningUsersLU());
+ verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
}
/**
@@ -530,7 +541,7 @@
*/
@Test
public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode()
- throws InterruptedException, RemoteException {
+ throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
@@ -645,6 +656,8 @@
setUpUser(TEST_USER_ID1, 0);
assertThrows(IllegalArgumentException.class,
() -> mUserController.startProfile(TEST_USER_ID1));
+
+ verifyUserNeverAssignedToDisplay();
}
@Test
@@ -660,6 +673,8 @@
setUpUser(TEST_USER_ID1, UserInfo.FLAG_PROFILE | UserInfo.FLAG_DISABLED, /* preCreated= */
false, UserManager.USER_TYPE_PROFILE_MANAGED);
assertThat(mUserController.startProfile(TEST_USER_ID1)).isFalse();
+
+ verifyUserNeverAssignedToDisplay();
}
@Test
@@ -949,6 +964,10 @@
verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
}
+ private void verifyOnUserStarting(@UserIdInt int userId, boolean visible) {
+ verify(mInjector).onUserStarting(userId, visible);
+ }
+
// Should be public to allow mocking
private static class TestInjector extends UserController.Injector {
public final TestHandler mHandler;
@@ -1084,6 +1103,11 @@
protected LockPatternUtils getLockPatternUtils() {
return mLockPatternUtilsMock;
}
+
+ @Override
+ void onUserStarting(@UserIdInt int userId, boolean visible) {
+ Log.i(TAG, "onUserStarting(" + userId + ", " + visible + ")");
+ }
}
private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index b33e22f..9acc4bd 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -49,13 +49,13 @@
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.AtomicFile;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.widget.RemoteViews;
import com.android.frameworks.servicestests.R;
import com.android.internal.appwidget.IAppWidgetHost;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import org.mockito.ArgumentCaptor;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index 68c9ce4..0cff4f1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -178,6 +178,23 @@
}
@Test
+ public void testWatchDogCompletesAwait() {
+ mProbe.enable();
+
+ AtomicInteger lux = new AtomicInteger(-9);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ verify(mSensorManager).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+ moveTimeBy(TIMEOUT_MS);
+
+ assertThat(lux.get()).isEqualTo(-1);
+ verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+ verifyNoMoreInteractions(mSensorManager);
+ }
+
+ @Test
public void testNextLuxWhenAlreadyEnabledAndNotAvailable() {
testNextLuxWhenAlreadyEnabled(false /* dataIsAvailable */);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 12b8264..41f7433 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -39,6 +39,7 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -63,6 +64,8 @@
private IFace mDaemon;
@Mock
private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricStateCallback mBiometricStateCallback;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -91,8 +94,8 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mSensorProps, TAG,
- mLockoutResetDispatcher, mBiometricContext);
+ mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mBiometricStateCallback,
+ mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext);
}
@SuppressWarnings("rawtypes")
@@ -140,11 +143,13 @@
TestableFaceProvider(@NonNull IFace daemon,
@NonNull Context context,
+ @NonNull BiometricStateCallback biometricStateCallback,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull BiometricContext biometricContext) {
- super(context, props, halInstanceName, lockoutResetDispatcher, biometricContext);
+ super(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
+ biometricContext);
mDaemon = daemon;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index 116d2d5..a2cade7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -43,6 +43,7 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import org.junit.Before;
@@ -73,6 +74,8 @@
private BiometricScheduler mScheduler;
@Mock
private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricStateCallback mBiometricStateCallback;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -103,8 +106,8 @@
resetLockoutRequiresChallenge);
Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
- mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler,
- mBiometricContext);
+ mFace10 = new Face10(mContext, mBiometricStateCallback, sensorProps,
+ mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
mBinder = new Binder();
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
index d58d71f..dc46ff8 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
@@ -23,13 +23,13 @@
import android.app.admin.FactoryResetProtectionPolicy;
import android.os.Parcel;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.FastXmlSerializer;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
index 72fac55..d540734 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
@@ -33,12 +33,12 @@
import android.os.IpcDataCache;
import android.os.Parcel;
import android.os.UserHandle;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
import com.android.internal.util.JournaledFile;
+import com.android.modules.utils.TypedXmlPullParser;
import com.android.server.SystemService;
import com.google.common.io.Files;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/SystemUpdatePolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/SystemUpdatePolicyTest.java
index 1308a3e..7588c79d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/SystemUpdatePolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/SystemUpdatePolicyTest.java
@@ -29,13 +29,13 @@
import android.app.admin.FreezePeriod;
import android.app.admin.SystemUpdatePolicy;
import android.os.Parcel;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.FastXmlSerializer;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 7a2a583..54baf18 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -590,11 +590,15 @@
@Test
public void handleReportAudioStatus_SamOnArcOff_setStreamVolumeNotCalled() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
+ HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED);
// Emulate Audio device on port 0x1000 (does not support ARC)
mNativeWrapper.setPortConnectionStatus(1, true);
HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mTestLooper.dispatchAll();
HdmiCecFeatureAction systemAudioAutoInitiationAction =
new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM);
@@ -821,6 +825,10 @@
@Test
public void receiveSetAudioVolumeLevel_samActivated_respondsFeatureAbort_noVolumeChange() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
+ HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED);
+
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode(
ADDR_AUDIO_SYSTEM, ADDR_TV, true));
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index c735d18..8f0fb0b 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -45,14 +45,14 @@
import android.os.RemoteException;
import android.os.SimpleClock;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.junit.After;
import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index 853eea1..ee97466 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -44,13 +44,13 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AtomicFile;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
import com.android.server.wm.ActivityTaskManagerInternal;
import org.junit.After;
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
index 0a26f27..3f7eac7 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
@@ -29,12 +29,13 @@
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.text.TextUtils;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
import androidx.annotation.NonNull;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index a545b1f..648f895 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -32,13 +32,13 @@
import android.platform.test.annotations.Presubmit;
import android.util.AtomicFile;
import android.util.Slog;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BackgroundThread;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
index 7e4474f..47f75a5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
@@ -25,13 +25,13 @@
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.platform.test.annotations.Presubmit;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.HexDump;
+import com.android.modules.utils.TypedXmlPullParser;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 4d03749..48d6d90 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -568,7 +568,6 @@
private static void assertBasicPackageScanResult(
ScanResult scanResult, String packageName, boolean isInstant) {
- assertThat(scanResult.mSuccess, is(true));
final PackageSetting pkgSetting = scanResult.mPkgSetting;
assertBasicPackageSetting(scanResult, packageName, isInstant, pkgSetting);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 867890f..96c0d0a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -98,13 +98,13 @@
import android.platform.test.annotations.Presubmit;
import android.util.Log;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.filters.SmallTest;
import com.android.frameworks.servicestests.R;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.ShortcutService.ConfigConstants;
import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
import com.android.server.pm.ShortcutUser.PackageWithUser;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 13a7a3e..8efcc41 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -23,13 +23,14 @@
import android.content.pm.UserProperties;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
index 1049274..970020f 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
@@ -27,13 +27,13 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.PowerProfile;
+import com.android.modules.utils.TypedXmlSerializer;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 067f4e2..e603ea5 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -37,13 +37,14 @@
import android.os.Parcel;
import android.os.UidBatteryConsumer;
import android.os.UserBatteryConsumer;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
index 7ac4938..9c61d95 100644
--- a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
@@ -21,10 +21,10 @@
import android.app.usage.CacheQuotaHint;
import android.test.AndroidTestCase;
import android.util.Pair;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.util.FastXmlSerializer;
+import com.android.modules.utils.TypedXmlSerializer;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 7986043..582e744 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -62,11 +62,11 @@
import android.util.ArraySet;
import android.util.IntArray;
import android.util.SparseArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.UiServiceTestCase;
import com.google.android.collect.Lists;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 4b93e35..9c68ddc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -46,10 +46,10 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
import com.android.internal.util.function.TriPredicate;
+import com.android.modules.utils.TypedXmlPullParser;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 248a3fc..581cf43 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -63,10 +63,10 @@
import android.testing.TestableContext;
import android.util.ArraySet;
import android.util.Pair;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.UiServiceTestCase;
import com.google.common.collect.ImmutableList;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 92761427..2ed8b10 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -193,8 +193,6 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Pair;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.widget.RemoteViews;
@@ -206,6 +204,8 @@
import com.android.internal.logging.InstanceIdSequenceFake;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
import com.android.server.SystemService;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 598a22b..0f93598 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -110,14 +110,14 @@
import android.util.IntArray;
import android.util.Pair;
import android.util.StatsEvent;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.os.AtomsProto.PackageNotificationPreferences;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.PermissionHelper.PackagePermission;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 7817e81..a03a1b4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -44,12 +44,12 @@
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.IntArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.UiServiceTestCase;
import com.android.server.pm.PackageManagerService;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 949455a1..2b6db14 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -33,12 +33,12 @@
import android.service.notification.ZenModeConfig.EventInfo;
import android.service.notification.ZenPolicy;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.UiServiceTestCase;
import org.junit.Test;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 2ccdcaa..49edde5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -105,12 +105,12 @@
import android.util.ArrayMap;
import android.util.Log;
import android.util.StatsEvent;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.R;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.ManagedServices.UserProfiles;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 8a18912..4ec9762 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -49,6 +49,7 @@
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_IME;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -146,7 +147,6 @@
import android.view.IWindowSession;
import android.view.InsetsSource;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.Surface;
@@ -2002,7 +2002,7 @@
doReturn(WindowManagerGlobal.ADD_STARTING_NOT_NEEDED).when(session).addToDisplay(
any() /* window */, any() /* attrs */,
anyInt() /* viewVisibility */, anyInt() /* displayId */,
- any() /* requestedVisibilities */, any() /* outInputChannel */,
+ anyInt() /* requestedVisibleTypes */, any() /* outInputChannel */,
any() /* outInsetsState */, any() /* outActiveControls */,
any() /* outAttachedFrame */, any() /* outSizeCompatScale */);
mAtm.mWindowManager.mStartingSurfaceController
@@ -3233,9 +3233,7 @@
app2.mActivityRecord.commitVisibility(false, false);
// app1 requests IME visible.
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_IME, true);
- app1.setRequestedVisibilities(requestedVisibilities);
+ app1.setRequestedVisibleTypes(ime(), ime());
mDisplayContent.getInsetsStateController().onInsetsModified(app1);
// Verify app1's IME insets is visible and app2's IME insets frozen flag set.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index e69418b..37ab9a0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -34,13 +34,14 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.DisplayCutout.fromBoundingRect;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.Surface.ROTATION_0;
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.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -130,7 +131,6 @@
import android.view.ISystemGestureExclusionListener;
import android.view.IWindowManager;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -1399,10 +1399,7 @@
win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
win.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
win.getAttrs().insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, false);
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
- win.setRequestedVisibilities(requestedVisibilities);
+ win.setRequestedVisibleTypes(0, navigationBars() | statusBars());
win.mActivityRecord.mTargetSdk = P;
performLayout(dc);
@@ -2486,7 +2483,7 @@
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
mDisplayContent.setImeLayeringTarget(imeAppTarget);
spyOn(imeAppTarget);
- doReturn(true).when(imeAppTarget).getRequestedVisibility(ITYPE_IME);
+ doReturn(true).when(imeAppTarget).isRequestedVisible(ime());
assertEquals(imeChildWindow, mDisplayContent.findFocusedWindow());
// Verify imeChildWindow doesn't be focused window if the next IME target does not
@@ -2511,7 +2508,7 @@
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
mDisplayContent.setImeLayeringTarget(imeAppTarget);
spyOn(imeAppTarget);
- doReturn(true).when(imeAppTarget).getRequestedVisibility(ITYPE_IME);
+ doReturn(true).when(imeAppTarget).isRequestedVisible(ime());
assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow());
// Verify imeMenuDialog doesn't be focused window if the next IME target is closing.
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 a980765..52af8ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -371,7 +371,7 @@
final InsetsControlTarget controlTarget = mock(InsetsControlTarget.class);
when(provider.getControlTarget()).thenReturn(controlTarget);
when(windowState.getControllableInsetProvider()).thenReturn(provider);
- when(controlTarget.getRequestedVisibility(anyInt())).thenReturn(true);
+ when(controlTarget.isRequestedVisible(anyInt())).thenReturn(true);
displayPolicy.setCanSystemBarsBeShownByUser(false);
displayPolicy.requestTransientBars(windowState, true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
index 18a1caa..9d839fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
@@ -29,7 +29,6 @@
import android.annotation.Nullable;
import android.platform.test.annotations.Presubmit;
-import android.util.TypedXmlPullParser;
import android.util.Xml;
import android.view.Display;
import android.view.DisplayAddress;
@@ -37,6 +36,7 @@
import androidx.test.filters.SmallTest;
+import com.android.modules.utils.TypedXmlPullParser;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
import org.junit.After;
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 f2bc47d..fd2a1d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -19,11 +19,15 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
@@ -51,7 +55,7 @@
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
import androidx.test.filters.SmallTest;
@@ -74,7 +78,7 @@
@Test
public void testControlsForDispatch_regular() {
addStatusBar();
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -86,7 +90,7 @@
@Test
public void testControlsForDispatch_multiWindowTaskVisible() {
addStatusBar();
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
final WindowState win = createWindow(null, WINDOWING_MODE_MULTI_WINDOW,
ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
@@ -99,7 +103,7 @@
@Test
public void testControlsForDispatch_freeformTaskVisible() {
addStatusBar();
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
@@ -112,7 +116,7 @@
@Test
public void testControlsForDispatch_forceStatusBarVisible() {
addStatusBar().mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -126,7 +130,7 @@
addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |=
PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
addStatusBar();
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -139,7 +143,7 @@
public void testControlsForDispatch_statusBarForceShowNavigation_butFocusedAnyways() {
WindowState notifShade = addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade");
notifShade.mAttrs.privateFlags |= PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
mDisplayContent.getInsetsPolicy().updateBarControlTarget(notifShade);
InsetsSourceControl[] controls
@@ -155,7 +159,7 @@
mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController());
mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(true);
addStatusBar();
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -166,13 +170,11 @@
@Test
public void testControlsForDispatch_topAppHidesStatusBar() {
addStatusBar();
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
// Add a fullscreen (MATCH_PARENT x MATCH_PARENT) app window which hides status bar.
final WindowState fullscreenApp = addWindow(TYPE_APPLICATION, "fullscreenApp");
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
- fullscreenApp.setRequestedVisibilities(requestedVisibilities);
+ fullscreenApp.setRequestedVisibleTypes(0, WindowInsets.Type.statusBars());
// Add a non-fullscreen dialog window.
final WindowState dialog = addWindow(TYPE_APPLICATION, "dialog");
@@ -205,9 +207,8 @@
// Assume mFocusedWindow is updated but mTopFullscreenOpaqueWindowState hasn't.
final WindowState newFocusedFullscreenApp = addWindow(TYPE_APPLICATION, "newFullscreenApp");
- final InsetsVisibilities newRequestedVisibilities = new InsetsVisibilities();
- newRequestedVisibilities.setVisibility(ITYPE_STATUS_BAR, true);
- newFocusedFullscreenApp.setRequestedVisibilities(newRequestedVisibilities);
+ newFocusedFullscreenApp.setRequestedVisibleTypes(
+ WindowInsets.Type.statusBars(), WindowInsets.Type.statusBars());
// Make sure status bar is hidden by previous insets state.
mDisplayContent.getInsetsPolicy().updateBarControlTarget(fullscreenApp);
@@ -261,17 +262,15 @@
final WindowState statusBar = addStatusBar();
statusBar.setHasSurface(true);
statusBar.getControllableInsetProvider().setServerVisible(true);
- final WindowState navBar = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar");
+ final WindowState navBar = addNavigationBar();
navBar.setHasSurface(true);
navBar.getControllableInsetProvider().setServerVisible(true);
final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
doNothing().when(policy).startAnimation(anyBoolean(), any());
// Make both system bars invisible.
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
- requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, false);
- mAppWindow.setRequestedVisibilities(requestedVisibilities);
+ mAppWindow.setRequestedVisibleTypes(
+ 0, navigationBars() | statusBars());
policy.updateBarControlTarget(mAppWindow);
waitUntilWindowAnimatorIdle();
assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
@@ -301,8 +300,7 @@
@Test
public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() {
addStatusBar().getControllableInsetProvider().getSource().setVisible(false);
- addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
- .getControllableInsetProvider().setServerVisible(true);
+ addNavigationBar().getControllableInsetProvider().setServerVisible(true);
final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
doNothing().when(policy).startAnimation(anyBoolean(), any());
@@ -331,8 +329,8 @@
public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() {
final InsetsSource statusBarSource =
addStatusBar().getControllableInsetProvider().getSource();
- final InsetsSource navBarSource = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
- .getControllableInsetProvider().getSource();
+ final InsetsSource navBarSource =
+ addNavigationBar().getControllableInsetProvider().getSource();
statusBarSource.setVisible(false);
navBarSource.setVisible(false);
mAppWindow.mAboveInsetsState.addSource(navBarSource);
@@ -364,10 +362,8 @@
assertTrue(state.getSource(ITYPE_STATUS_BAR).isVisible());
assertTrue(state.getSource(ITYPE_NAVIGATION_BAR).isVisible());
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, true);
- requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, true);
- mAppWindow.setRequestedVisibilities(requestedVisibilities);
+ mAppWindow.setRequestedVisibleTypes(
+ navigationBars() | statusBars(), navigationBars() | statusBars());
policy.onInsetsModified(mAppWindow);
waitUntilWindowAnimatorIdle();
@@ -383,8 +379,7 @@
@Test
public void testShowTransientBars_abortsWhenControlTargetChanges() {
addStatusBar().getControllableInsetProvider().getSource().setVisible(false);
- addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
- .getControllableInsetProvider().getSource().setVisible(false);
+ addNavigationBar().getControllableInsetProvider().getSource().setVisible(false);
final WindowState app = addWindow(TYPE_APPLICATION, "app");
final WindowState app2 = addWindow(TYPE_APPLICATION, "app");
@@ -400,9 +395,15 @@
assertFalse(policy.isTransient(ITYPE_NAVIGATION_BAR));
}
- private WindowState addNonFocusableWindow(int type, String name) {
- WindowState win = addWindow(type, name);
+ private WindowState addNavigationBar() {
+ final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+ win.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_NAVIGATION_BAR),
+ new InsetsFrameProvider(ITYPE_BOTTOM_MANDATORY_GESTURES),
+ new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT)
+ };
+ mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
return win;
}
@@ -429,6 +430,10 @@
}
private InsetsSourceControl[] addWindowAndGetControlsForDispatch(WindowState win) {
+ mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
+ // Force update the focus in DisplayPolicy here. Otherwise, without server side focus
+ // update, the policy relying on windowing type will never get updated.
+ mDisplayContent.getDisplayPolicy().focusChangedLw(null, win);
mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
return mDisplayContent.getInsetsStateController().getControlsForDispatch(win);
}
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 fe14d8e..c898119 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -24,6 +24,7 @@
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -48,7 +49,6 @@
import android.util.SparseArray;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import androidx.test.filters.SmallTest;
@@ -187,10 +187,7 @@
getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
getController().onImeControlTargetChanged(
mDisplayContent.getImeInputTarget().getWindowState());
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_IME, true);
- mDisplayContent.getImeInputTarget().getWindowState()
- .setRequestedVisibilities(requestedVisibilities);
+ mDisplayContent.getImeInputTarget().getWindowState().setRequestedVisibleTypes(ime(), ime());
getController().onInsetsModified(mDisplayContent.getImeInputTarget().getWindowState());
// Send our spy window (app) into the system so that we can detect the invocation.
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
new file mode 100644
index 0000000..1246d1e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+import static com.android.server.wm.LetterboxConfigurationPersister.LETTERBOX_CONFIGURATION_FILENAME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.util.AtomicFile;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+@SmallTest
+@Presubmit
+public class LetterboxConfigurationPersisterTest {
+
+ private static final long TIMEOUT = 2000L; // 2 secs
+
+ private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+ private Context mContext;
+ private PersisterQueue mPersisterQueue;
+ private QueueState mQueueState;
+ private PersisterQueue.Listener mQueueListener;
+ private File mConfigFolder;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = getInstrumentation().getTargetContext();
+ mConfigFolder = mContext.getFilesDir();
+ mPersisterQueue = new PersisterQueue();
+ mQueueState = new QueueState();
+ mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(mContext,
+ () -> mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability),
+ () -> mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForVerticalReachability),
+ mConfigFolder, mPersisterQueue, mQueueState);
+ mQueueListener = queueEmpty -> mQueueState.onItemAdded();
+ mPersisterQueue.addListener(mQueueListener);
+ mLetterboxConfigurationPersister.start();
+ }
+
+ public void tearDown() throws InterruptedException {
+ deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
+ waitForCompletion(mPersisterQueue);
+ mPersisterQueue.removeListener(mQueueListener);
+ stopPersisterSafe(mPersisterQueue);
+ }
+
+ @Test
+ public void test_whenStoreIsCreated_valuesAreDefaults() {
+ final int positionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int defaultPositionForHorizontalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability);
+ Assert.assertEquals(defaultPositionForHorizontalReachability,
+ positionForHorizontalReachability);
+ final int positionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ final int defaultPositionForVerticalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForVerticalReachability);
+ Assert.assertEquals(defaultPositionForVerticalReachability,
+ positionForVerticalReachability);
+ }
+
+ @Test
+ public void test_whenUpdatedWithNewValues_valuesAreWritten() {
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ waitForCompletion(mPersisterQueue);
+ final int newPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int newPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ newPositionForHorizontalReachability);
+ Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ newPositionForVerticalReachability);
+ }
+
+ @Test
+ public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() {
+ final PersisterQueue firstPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
+ mContext, () -> -1, () -> -1, mContext.getFilesDir(), firstPersisterQueue,
+ mQueueState);
+ firstPersister.start();
+ firstPersister.setLetterboxPositionForHorizontalReachability(
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ firstPersister.setLetterboxPositionForVerticalReachability(
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ waitForCompletion(firstPersisterQueue);
+ stopPersisterSafe(firstPersisterQueue);
+ final PersisterQueue secondPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
+ mContext, () -> -1, () -> -1, mContext.getFilesDir(), secondPersisterQueue,
+ mQueueState);
+ secondPersister.start();
+ final int newPositionForHorizontalReachability =
+ secondPersister.getLetterboxPositionForHorizontalReachability();
+ final int newPositionForVerticalReachability =
+ secondPersister.getLetterboxPositionForVerticalReachability();
+ Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ newPositionForHorizontalReachability);
+ Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ newPositionForVerticalReachability);
+ deleteConfiguration(secondPersister, secondPersisterQueue);
+ waitForCompletion(secondPersisterQueue);
+ stopPersisterSafe(secondPersisterQueue);
+ }
+
+ @Test
+ public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() {
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ waitForCompletion(mPersisterQueue);
+ final int newPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int newPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ newPositionForHorizontalReachability);
+ Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ newPositionForVerticalReachability);
+ deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
+ waitForCompletion(mPersisterQueue);
+ final int positionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int defaultPositionForHorizontalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability);
+ Assert.assertEquals(defaultPositionForHorizontalReachability,
+ positionForHorizontalReachability);
+ final int positionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ final int defaultPositionForVerticalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForVerticalReachability);
+ Assert.assertEquals(defaultPositionForVerticalReachability,
+ positionForVerticalReachability);
+ }
+
+ private void stopPersisterSafe(PersisterQueue persisterQueue) {
+ try {
+ persisterQueue.stopPersisting();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void waitForCompletion(PersisterQueue persisterQueue) {
+ final long endTime = System.currentTimeMillis() + TIMEOUT;
+ // The queue could be empty but the last item still processing and not completed. For this
+ // reason the completion happens when there are not more items to process and the last one
+ // has completed.
+ while (System.currentTimeMillis() < endTime && (!isQueueEmpty(persisterQueue)
+ || !hasLastItemCompleted())) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ie) { /* Nope */}
+ }
+ }
+
+ private boolean isQueueEmpty(PersisterQueue persisterQueue) {
+ return persisterQueue.findLastItem(
+ writeQueueItem -> true, PersisterQueue.WriteQueueItem.class) != null;
+ }
+
+ private boolean hasLastItemCompleted() {
+ return mQueueState.isEmpty();
+ }
+
+ private void deleteConfiguration(LetterboxConfigurationPersister persister,
+ PersisterQueue persisterQueue) {
+ final AtomicFile fileToDelete = new AtomicFile(
+ new File(mConfigFolder, LETTERBOX_CONFIGURATION_FILENAME));
+ persisterQueue.addItem(
+ new DeleteFileCommand(fileToDelete, mQueueState.andThen(
+ s -> persister.useDefaultValue())), true);
+ }
+
+ private static class DeleteFileCommand implements
+ PersisterQueue.WriteQueueItem<DeleteFileCommand> {
+
+ @NonNull
+ private final AtomicFile mFileToDelete;
+ @Nullable
+ private final Consumer<String> mOnComplete;
+
+ DeleteFileCommand(@NonNull AtomicFile fileToDelete, Consumer<String> onComplete) {
+ mFileToDelete = fileToDelete;
+ mOnComplete = onComplete;
+ }
+
+ @Override
+ public void process() {
+ mFileToDelete.delete();
+ if (mOnComplete != null) {
+ mOnComplete.accept("DeleteFileCommand");
+ }
+ }
+ }
+
+ // Contains the current length of the persister queue
+ private static class QueueState implements Consumer<String> {
+
+ // The current number of commands in the queue
+ @VisibleForTesting
+ private final AtomicInteger mCounter = new AtomicInteger(0);
+
+ @Override
+ public void accept(String s) {
+ mCounter.decrementAndGet();
+ }
+
+ void onItemAdded() {
+ mCounter.incrementAndGet();
+ }
+
+ boolean isEmpty() {
+ return mCounter.get() == 0;
+ }
+
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
new file mode 100644
index 0000000..c927f9e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.function.Consumer;
+
+@SmallTest
+@Presubmit
+public class LetterboxConfigurationTest {
+
+ private LetterboxConfiguration mLetterboxConfiguration;
+ private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = getInstrumentation().getTargetContext();
+ mLetterboxConfigurationPersister = mock(LetterboxConfigurationPersister.class);
+ mLetterboxConfiguration = new LetterboxConfiguration(context,
+ mLetterboxConfigurationPersister);
+ }
+
+ @Test
+ public void test_whenReadingValues_storeIsInvoked() {
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability();
+ verify(mLetterboxConfigurationPersister).getLetterboxPositionForHorizontalReachability();
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability();
+ verify(mLetterboxConfigurationPersister).getLetterboxPositionForVerticalReachability();
+ }
+
+ @Test
+ public void test_whenSettingValues_updateConfigurationIsInvoked() {
+ mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+ anyInt());
+ mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+ anyInt());
+ }
+
+ @Test
+ public void test_whenMovedHorizontally_updatePositionAccordingly() {
+ // Starting from center
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ // Starting from left
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ // Starting from right
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ }
+
+ @Test
+ public void test_whenMovedVertically_updatePositionAccordingly() {
+ // Starting from center
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ // Starting from top
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ // Starting from bottom
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ }
+
+ private void assertForHorizontalMove(int from, int expected, int expectedTime,
+ Consumer<LetterboxConfiguration> move) {
+ // We are in the current position
+ when(mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability())
+ .thenReturn(from);
+ move.accept(mLetterboxConfiguration);
+ verify(mLetterboxConfigurationPersister,
+ times(expectedTime)).setLetterboxPositionForHorizontalReachability(
+ expected);
+ }
+
+ private void assertForVerticalMove(int from, int expected, int expectedTime,
+ Consumer<LetterboxConfiguration> move) {
+ // We are in the current position
+ when(mLetterboxConfiguration.getLetterboxPositionForVerticalReachability())
+ .thenReturn(from);
+ move.accept(mLetterboxConfiguration);
+ verify(mLetterboxConfigurationPersister,
+ times(expectedTime)).setLetterboxPositionForVerticalReachability(
+ expected);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 0b23359..4202f46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -56,6 +56,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -91,6 +92,7 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import java.util.List;
@@ -762,6 +764,50 @@
}
@Test
+ public void testOrganizerRemovedWithPendingEvents() {
+ final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
+ .setCreateParentTask()
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(mFragmentToken)
+ .build();
+ final TaskFragment tf1 = new TaskFragmentBuilder(mAtm)
+ .setCreateParentTask()
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(new Binder())
+ .build();
+ assertTrue(tf0.isOrganizedTaskFragment());
+ assertTrue(tf1.isOrganizedTaskFragment());
+ assertTrue(tf0.isAttached());
+ assertTrue(tf0.isAttached());
+
+ // Mock the behavior that remove TaskFragment can trigger event dispatch.
+ final Answer<Void> removeImmediately = invocation -> {
+ invocation.callRealMethod();
+ mController.dispatchPendingEvents();
+ return null;
+ };
+ doAnswer(removeImmediately).when(tf0).removeImmediately();
+ doAnswer(removeImmediately).when(tf1).removeImmediately();
+
+ // Add pending events.
+ mController.onTaskFragmentAppeared(mIOrganizer, tf0);
+ mController.onTaskFragmentAppeared(mIOrganizer, tf1);
+
+ // Remove organizer.
+ mController.unregisterOrganizer(mIOrganizer);
+ mController.dispatchPendingEvents();
+
+ // Nothing should happen after the organizer is removed.
+ verify(mOrganizer, never()).onTransactionReady(any());
+
+ // TaskFragments should be removed.
+ assertFalse(tf0.isOrganizedTaskFragment());
+ assertFalse(tf1.isOrganizedTaskFragment());
+ assertFalse(tf0.isAttached());
+ assertFalse(tf0.isAttached());
+ }
+
+ @Test
public void testTaskFragmentInPip_startActivityInTaskFragment() {
setupTaskFragmentInPip();
final ActivityRecord activity = mTaskFragment.getTopMostActivity();
@@ -874,29 +920,87 @@
@Test
public void testDeferPendingTaskFragmentEventsOfInvisibleTask() {
- // Task - TaskFragment - Activity.
final Task task = createTask(mDisplayContent);
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setOrganizer(mOrganizer)
.setFragmentToken(mFragmentToken)
.build();
-
- // Mock the task to invisible
doReturn(false).when(task).shouldBeVisible(any());
- // Sending events
- taskFragment.mTaskFragmentAppearedSent = true;
+ // Dispatch the initial event in the Task to update the Task visibility to the organizer.
+ mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
+ mController.dispatchPendingEvents();
+ verify(mOrganizer).onTransactionReady(any());
+
+ // Verify that events were not sent when the Task is in background.
+ clearInvocations(mOrganizer);
+ final Rect bounds = new Rect(0, 0, 500, 1000);
+ task.setBoundsUnchecked(bounds);
+ mController.onTaskFragmentParentInfoChanged(mIOrganizer, task);
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
-
- // Verifies that event was not sent
verify(mOrganizer, never()).onTransactionReady(any());
+
+ // Verify that the events were sent when the Task becomes visible.
+ doReturn(true).when(task).shouldBeVisible(any());
+ task.lastActiveTime++;
+ mController.dispatchPendingEvents();
+ verify(mOrganizer).onTransactionReady(any());
+ }
+
+ @Test
+ public void testSendAllPendingTaskFragmentEventsWhenAnyTaskIsVisible() {
+ // Invisible Task.
+ final Task invisibleTask = createTask(mDisplayContent);
+ final TaskFragment invisibleTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(invisibleTask)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(mFragmentToken)
+ .build();
+ doReturn(false).when(invisibleTask).shouldBeVisible(any());
+
+ // Visible Task.
+ final IBinder fragmentToken = new Binder();
+ final Task visibleTask = createTask(mDisplayContent);
+ final TaskFragment visibleTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(visibleTask)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(fragmentToken)
+ .build();
+ doReturn(true).when(invisibleTask).shouldBeVisible(any());
+
+ // Sending events
+ invisibleTaskFragment.mTaskFragmentAppearedSent = true;
+ visibleTaskFragment.mTaskFragmentAppearedSent = true;
+ mController.onTaskFragmentInfoChanged(mIOrganizer, invisibleTaskFragment);
+ mController.onTaskFragmentInfoChanged(mIOrganizer, visibleTaskFragment);
+ mController.dispatchPendingEvents();
+
+ // Verify that both events are sent.
+ verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
+ final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
+ final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
+
+ // There should be two Task info changed with two TaskFragment info changed.
+ assertEquals(4, changes.size());
+ // Invisible Task info changed
+ assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, changes.get(0).getType());
+ assertEquals(invisibleTask.mTaskId, changes.get(0).getTaskId());
+ // Invisible TaskFragment info changed
+ assertEquals(TYPE_TASK_FRAGMENT_INFO_CHANGED, changes.get(1).getType());
+ assertEquals(invisibleTaskFragment.getFragmentToken(),
+ changes.get(1).getTaskFragmentToken());
+ // Visible Task info changed
+ assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, changes.get(2).getType());
+ assertEquals(visibleTask.mTaskId, changes.get(2).getTaskId());
+ // Visible TaskFragment info changed
+ assertEquals(TYPE_TASK_FRAGMENT_INFO_CHANGED, changes.get(3).getType());
+ assertEquals(visibleTaskFragment.getFragmentToken(), changes.get(3).getTaskFragmentToken());
}
@Test
public void testCanSendPendingTaskFragmentEventsAfterActivityResumed() {
- // Task - TaskFragment - Activity.
final Task task = createTask(mDisplayContent);
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
@@ -905,24 +1009,26 @@
.createActivityCount(1)
.build();
final ActivityRecord activity = taskFragment.getTopMostActivity();
-
- // Mock the task to invisible
doReturn(false).when(task).shouldBeVisible(any());
taskFragment.setResumedActivity(null, "test");
- // Sending events
- taskFragment.mTaskFragmentAppearedSent = true;
+ // Dispatch the initial event in the Task to update the Task visibility to the organizer.
+ mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
+ mController.dispatchPendingEvents();
+ verify(mOrganizer).onTransactionReady(any());
+
+ // Verify the info changed event is not sent because the Task is invisible
+ clearInvocations(mOrganizer);
+ final Rect bounds = new Rect(0, 0, 500, 1000);
+ task.setBoundsUnchecked(bounds);
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
-
- // Verifies that event was not sent
verify(mOrganizer, never()).onTransactionReady(any());
- // Mock the task becomes visible, and activity resumed
+ // Mock the task becomes visible, and activity resumed. Verify the info changed event is
+ // sent.
doReturn(true).when(task).shouldBeVisible(any());
taskFragment.setResumedActivity(activity, "test");
-
- // Verifies that event is sent.
mController.dispatchPendingEvents();
verify(mOrganizer).onTransactionReady(any());
}
@@ -977,25 +1083,24 @@
final ActivityRecord embeddedActivity = taskFragment.getTopNonFinishingActivity();
// Add another activity in the Task so that it always contains a non-finishing activity.
createActivityRecord(task);
- assertTrue(task.shouldBeVisible(null));
+ doReturn(false).when(task).shouldBeVisible(any());
- // Dispatch pending info changed event from creating the activity
- taskFragment.mTaskFragmentAppearedSent = true;
- mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
+ // Dispatch the initial event in the Task to update the Task visibility to the organizer.
+ mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
verify(mOrganizer).onTransactionReady(any());
- // Verify the info changed callback is not called when the task is invisible
+ // Verify the info changed event is not sent because the Task is invisible
clearInvocations(mOrganizer);
- doReturn(false).when(task).shouldBeVisible(any());
+ final Rect bounds = new Rect(0, 0, 500, 1000);
+ task.setBoundsUnchecked(bounds);
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
verify(mOrganizer, never()).onTransactionReady(any());
- // Finish the embedded activity, and verify the info changed callback is called because the
+ // Finish the embedded activity, and verify the info changed event is sent because the
// TaskFragment is becoming empty.
embeddedActivity.finishing = true;
- mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
verify(mOrganizer).onTransactionReady(any());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 92c9e80..66bf78b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -77,14 +77,15 @@
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.Display;
import android.view.DisplayInfo;
import androidx.test.filters.MediumTest;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 29a514c..d4c9087 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -60,7 +60,9 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.app.ActivityManager;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
@@ -80,6 +82,8 @@
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
+import com.android.internal.graphics.ColorUtils;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -1387,6 +1391,50 @@
}
@Test
+ public void testChangeSetBackgroundColor() {
+ final Transition transition = createTestTransition(TRANSIT_CHANGE);
+ final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ // Test background color for Activity and embedded TaskFragment.
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
+ final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
+ final ActivityManager.TaskDescription taskDescription =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW)
+ .build();
+ task.setTaskDescription(taskDescription);
+
+ // Start states:
+ embeddedActivity.mVisibleRequested = true;
+ nonEmbeddedActivity.mVisibleRequested = false;
+ changes.put(embeddedTf, new Transition.ChangeInfo(embeddedTf));
+ changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(nonEmbeddedActivity));
+ // End states:
+ embeddedActivity.mVisibleRequested = false;
+ nonEmbeddedActivity.mVisibleRequested = true;
+
+ participants.add(embeddedTf);
+ participants.add(nonEmbeddedActivity);
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ participants, changes);
+ final TransitionInfo info = Transition.calculateTransitionInfo(transition.mType,
+ 0 /* flags */, targets, changes, mMockT);
+
+ // Background color should be set on both Activity and embedded TaskFragment.
+ final int expectedBackgroundColor = ColorUtils.setAlphaComponent(
+ taskDescription.getBackgroundColor(), 255);
+ assertEquals(2, info.getChanges().size());
+ assertEquals(expectedBackgroundColor, info.getChanges().get(0).getBackgroundColor());
+ assertEquals(expectedBackgroundColor, info.getChanges().get(1).getBackgroundColor());
+ }
+
+ @Test
public void testTransitionVisibleChange() {
registerTestTransitionPlayer();
final ActivityRecord app = createActivityRecord(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
index e824f3d..383722a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
@@ -18,6 +18,7 @@
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -31,7 +32,6 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.InsetsSource;
-import android.view.InsetsVisibilities;
import androidx.test.filters.SmallTest;
@@ -207,9 +207,7 @@
statusBar.getFrame().set(0, 0, 500, 100);
mProvider.setWindowContainer(statusBar, null, null);
mProvider.updateControlForTarget(target, false /* force */);
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
- target.setRequestedVisibilities(requestedVisibilities);
+ target.setRequestedVisibleTypes(0, statusBars());
mProvider.updateClientVisibility(target);
assertFalse(mSource.isVisible());
}
@@ -220,9 +218,7 @@
final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
statusBar.getFrame().set(0, 0, 500, 100);
mProvider.setWindowContainer(statusBar, null, null);
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
- target.setRequestedVisibilities(requestedVisibilities);
+ target.setRequestedVisibleTypes(0, statusBars());
mProvider.updateClientVisibility(target);
assertTrue(mSource.isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
index 739e783..56c59cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
@@ -41,7 +41,6 @@
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.WindowInsets;
import android.view.WindowLayout;
import android.view.WindowManager;
@@ -81,7 +80,7 @@
private int mWindowingMode;
private int mRequestedWidth;
private int mRequestedHeight;
- private InsetsVisibilities mRequestedVisibilities;
+ private int mRequestedVisibleTypes;
private float mCompatScale;
@Before
@@ -98,14 +97,14 @@
mWindowingMode = WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
mRequestedWidth = DISPLAY_WIDTH;
mRequestedHeight = DISPLAY_HEIGHT;
- mRequestedVisibilities = new InsetsVisibilities();
+ mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
mCompatScale = 1f;
mFrames.attachedFrame = null;
}
private void computeFrames() {
mWindowLayout.computeFrames(mAttrs, mState, mDisplayCutoutSafe, mWindowBounds,
- mWindowingMode, mRequestedWidth, mRequestedHeight, mRequestedVisibilities,
+ mWindowingMode, mRequestedWidth, mRequestedHeight, mRequestedVisibleTypes,
mCompatScale, mFrames);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index cf24ff2..4429aef 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -71,10 +71,10 @@
import android.view.IWindowSessionCallback;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.ClientWindowFrames;
import android.window.ScreenCapture;
@@ -338,7 +338,7 @@
.getWindowType(eq(windowContextToken));
mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY,
- UserHandle.USER_SYSTEM, new InsetsVisibilities(), null, new InsetsState(),
+ UserHandle.USER_SYSTEM, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
new InsetsSourceControl[0], new Rect(), new float[1]);
verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 1636667..0139f6a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -26,6 +26,7 @@
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -92,7 +93,6 @@
import android.view.InputWindowHandle;
import android.view.InsetsSource;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -430,9 +430,7 @@
null /* imeFrameProvider */);
mDisplayContent.getInsetsStateController().onBarControlTargetChanged(
app, null /* fakeTopControlling */, app, null /* fakeNavControlling */);
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
- app.setRequestedVisibilities(requestedVisibilities);
+ app.setRequestedVisibleTypes(0, statusBars());
mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR)
.updateClientVisibility(app);
waitUntilHandlersIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 40326e9..eca7cbb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -26,6 +26,8 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.os.Process.SYSTEM_UID;
+import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
@@ -92,7 +94,6 @@
import android.view.InsetsFrameProvider;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -347,6 +348,11 @@
LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mNavBarWindow.mAttrs.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+ mNavBarWindow.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_NAVIGATION_BAR),
+ new InsetsFrameProvider(ITYPE_BOTTOM_MANDATORY_GESTURES),
+ new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT)
+ };
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
mNavBarWindow.mAttrs.paramsForRotation[rot] =
getNavBarLayoutParamsForRotation(rot);
@@ -400,6 +406,11 @@
lp.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ lp.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_NAVIGATION_BAR),
+ new InsetsFrameProvider(ITYPE_BOTTOM_MANDATORY_GESTURES),
+ new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT)
+ };
return lp;
}
@@ -846,7 +857,7 @@
@Override
public void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ int requestedVisibleTypes) {
}
};
}
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index bb0c4e9..47b09fe 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -53,8 +53,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -62,6 +60,8 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index dd5f153..f39cb39 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -48,13 +48,13 @@
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseBooleanArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 921f6e2..372fdaf 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -129,7 +129,10 @@
private static final String KEY_RESTART_PERIOD_IN_SECONDS = "restart_period_in_seconds";
// TODO: These constants need to be refined.
- private static final long VALIDATION_TIMEOUT_MILLIS = 4000;
+ // The validation timeout value is 3 seconds for onDetect of DSP trigger event.
+ private static final long VALIDATION_TIMEOUT_MILLIS = 3000;
+ // Write the onDetect timeout metric when it takes more time than MAX_VALIDATION_TIMEOUT_MILLIS.
+ private static final long MAX_VALIDATION_TIMEOUT_MILLIS = 4000;
private static final long MAX_UPDATE_TIMEOUT_MILLIS = 30000;
private static final long EXTERNAL_HOTWORD_CLEANUP_MILLIS = 2000;
private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
@@ -659,6 +662,10 @@
synchronized (mLock) {
mValidatingDspTrigger = true;
mRemoteHotwordDetectionService.run(service -> {
+ // We use the VALIDATION_TIMEOUT_MILLIS to inform that the client needs to invoke
+ // the callback before timeout value. In order to reduce the latency impact between
+ // server side and client side, we need to use another timeout value
+ // MAX_VALIDATION_TIMEOUT_MILLIS to monitor it.
mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule(
() -> {
// TODO: avoid allocate every time
@@ -673,7 +680,7 @@
Slog.w(TAG, "Failed to report onError status: ", e);
}
},
- VALIDATION_TIMEOUT_MILLIS,
+ MAX_VALIDATION_TIMEOUT_MILLIS,
TimeUnit.MILLISECONDS);
service.detectFromDspSource(
recognitionEvent,
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 439eaa6..ef693b5 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3942,6 +3942,10 @@
* may provide one. Or, a carrier may decide to provide the phone number via source
* {@link #PHONE_NUMBER_SOURCE_CARRIER carrier} if neither source UICC nor IMS is available.
*
+ * <p>The availability and correctness of the phone number depends on the underlying source
+ * and the network etc. Additional verification is needed to use this number for
+ * security-related or other sensitive scenarios.
+ *
* @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
* for the default one.
* @param source the source of the phone number, one of the PHONE_NUMBER_SOURCE_* constants.
@@ -4175,18 +4179,18 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
- public void setUserHandle(int subscriptionId, @Nullable UserHandle userHandle) {
+ public void setSubscriptionUserHandle(int subscriptionId, @Nullable UserHandle userHandle) {
if (!isValidSubscriptionId(subscriptionId)) {
- throw new IllegalArgumentException("[setUserHandle]: Invalid subscriptionId: "
- + subscriptionId);
+ throw new IllegalArgumentException("[setSubscriptionUserHandle]: "
+ + "Invalid subscriptionId: " + subscriptionId);
}
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- iSub.setUserHandle(userHandle, subscriptionId, mContext.getOpPackageName());
+ iSub.setSubscriptionUserHandle(userHandle, subscriptionId);
} else {
- throw new IllegalStateException("[setUserHandle]: "
+ throw new IllegalStateException("[setSubscriptionUserHandle]: "
+ "subscription service unavailable");
}
} catch (RemoteException ex) {
@@ -4211,18 +4215,18 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
- public @Nullable UserHandle getUserHandle(int subscriptionId) {
+ public @Nullable UserHandle getSubscriptionUserHandle(int subscriptionId) {
if (!isValidSubscriptionId(subscriptionId)) {
- throw new IllegalArgumentException("[getUserHandle]: Invalid subscriptionId: "
- + subscriptionId);
+ throw new IllegalArgumentException("[getSubscriptionUserHandle]: "
+ + "Invalid subscriptionId: " + subscriptionId);
}
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- return iSub.getUserHandle(subscriptionId, mContext.getOpPackageName());
+ return iSub.getSubscriptionUserHandle(subscriptionId);
} else {
- throw new IllegalStateException("[getUserHandle]: "
+ throw new IllegalStateException("[getSubscriptionUserHandle]: "
+ "subscription service unavailable");
}
} catch (RemoteException ex) {
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 0211a7f..4752cca 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -323,22 +323,20 @@
*
* @param userHandle the user handle for this subscription
* @param subId the unique SubscriptionInfo index in database
- * @param callingPackage The package making the IPC.
*
* @throws SecurityException if doesn't have MANAGE_SUBSCRIPTION_USER_ASSOCIATION
* @throws IllegalArgumentException if subId is invalid.
*/
- int setUserHandle(in UserHandle userHandle, int subId, String callingPackage);
+ int setSubscriptionUserHandle(in UserHandle userHandle, int subId);
/**
* Get UserHandle for this subscription
*
* @param subId the unique SubscriptionInfo index in database
- * @param callingPackage the package making the IPC
* @return userHandle associated with this subscription.
*
- * @throws SecurityException if doesn't have SMANAGE_SUBSCRIPTION_USER_ASSOCIATION
+ * @throws SecurityException if doesn't have MANAGE_SUBSCRIPTION_USER_ASSOCIATION
* @throws IllegalArgumentException if subId is invalid.
*/
- UserHandle getUserHandle(int subId, String callingPackage);
+ UserHandle getSubscriptionUserHandle(int subId);
}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 96bbf82..f8d885a 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -44,14 +44,14 @@
import android.provider.DeviceConfig;
import android.util.AtomicFile;
import android.util.LongArrayQueue;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.PackageWatchdog.HealthCheckState;
import com.android.server.PackageWatchdog.MonitoredPackage;
import com.android.server.PackageWatchdog.PackageHealthObserver;
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 5cee17e..df09e47 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -30,6 +30,91 @@
namespace aapt {
+// This is to detect whether an <intent-filter> contains deeplink.
+// See https://developer.android.com/training/app-links/deep-linking.
+static bool HasDeepLink(xml::Element* intent_filter_el) {
+ xml::Element* action_el = intent_filter_el->FindChild({}, "action");
+ xml::Element* category_el = intent_filter_el->FindChild({}, "category");
+ xml::Element* data_el = intent_filter_el->FindChild({}, "data");
+ if (action_el == nullptr || category_el == nullptr || data_el == nullptr) {
+ return false;
+ }
+
+ // Deeplinks must specify the ACTION_VIEW intent action.
+ constexpr const char* action_view = "android.intent.action.VIEW";
+ if (intent_filter_el->FindChildWithAttribute({}, "action", xml::kSchemaAndroid, "name",
+ action_view) == nullptr) {
+ return false;
+ }
+
+ // Deeplinks must have scheme included in <data> tag.
+ xml::Attribute* data_scheme_attr = data_el->FindAttribute(xml::kSchemaAndroid, "scheme");
+ if (data_scheme_attr == nullptr || data_scheme_attr->value.empty()) {
+ return false;
+ }
+
+ // Deeplinks must include BROWSABLE category.
+ constexpr const char* category_browsable = "android.intent.category.BROWSABLE";
+ if (intent_filter_el->FindChildWithAttribute({}, "category", xml::kSchemaAndroid, "name",
+ category_browsable) == nullptr) {
+ return false;
+ }
+ return true;
+}
+
+static bool VerifyDeeplinkPathAttribute(xml::Element* data_el, android::SourcePathDiagnostics* diag,
+ const std::string& attr_name) {
+ xml::Attribute* attr = data_el->FindAttribute(xml::kSchemaAndroid, attr_name);
+ if (attr != nullptr && !attr->value.empty()) {
+ StringPiece attr_value = attr->value;
+ const char* startChar = attr_value.begin();
+ if (attr_name == "pathPattern") {
+ if (*startChar == '/' || *startChar == '.' || *startChar == '*') {
+ return true;
+ } else {
+ diag->Error(android::DiagMessage(data_el->line_number)
+ << "attribute 'android:" << attr_name << "' in <" << data_el->name
+ << "> tag has value of '" << attr_value
+ << "', it must be in a pattern start with '.' or '*', otherwise must start "
+ "with a leading slash '/'");
+ return false;
+ }
+ } else {
+ if (*startChar == '/') {
+ return true;
+ } else {
+ diag->Error(android::DiagMessage(data_el->line_number)
+ << "attribute 'android:" << attr_name << "' in <" << data_el->name
+ << "> tag has value of '" << attr_value
+ << "', it must start with a leading slash '/'");
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static bool VerifyDeepLinkIntentAction(xml::Element* intent_filter_el,
+ android::SourcePathDiagnostics* diag) {
+ if (!HasDeepLink(intent_filter_el)) {
+ return true;
+ }
+
+ xml::Element* data_el = intent_filter_el->FindChild({}, "data");
+ if (data_el != nullptr) {
+ if (!VerifyDeeplinkPathAttribute(data_el, diag, "path")) {
+ return false;
+ }
+ if (!VerifyDeeplinkPathAttribute(data_el, diag, "pathPrefix")) {
+ return false;
+ }
+ if (!VerifyDeeplinkPathAttribute(data_el, diag, "pathPattern")) {
+ return false;
+ }
+ }
+ return true;
+}
+
static bool RequiredNameIsNotEmpty(xml::Element* el, android::SourcePathDiagnostics* diag) {
xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
if (attr == nullptr) {
@@ -323,6 +408,7 @@
// Common <intent-filter> actions.
xml::XmlNodeAction intent_filter_action;
+ intent_filter_action.Action(VerifyDeepLinkIntentAction);
intent_filter_action["action"].Action(RequiredNameIsNotEmpty);
intent_filter_action["category"].Action(RequiredNameIsNotEmpty);
intent_filter_action["data"];
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 098d0be..cec9a1a 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -1068,4 +1068,345 @@
</manifest>)";
EXPECT_THAT(Verify(input), NotNull());
}
+
+TEST_F(ManifestFixerTest, IntentFilterActionMustHaveNonEmptyName) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+}
+
+TEST_F(ManifestFixerTest, IntentFilterCategoryMustHaveNonEmptyName) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <category android:name="" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <category />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+}
+
+TEST_F(ManifestFixerTest, IntentFilterPathMustStartWithLeadingSlashOnDeepLinks) {
+ // No DeepLink.
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <data />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // No DeepLink, missing ACTION_VIEW.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPrefix="pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // DeepLink, missing DEFAULT category while DEFAULT is recommended but not required.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPrefix="pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ // No DeepLink, missing BROWSABLE category.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPrefix="pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // No DeepLink, missing 'android:scheme' in <data> tag.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:host="www.example.com"
+ android:pathPrefix="pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // No DeepLink, <action> is ACTION_MAIN not ACTION_VIEW.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPrefix="pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // DeepLink with no leading slash in android:path.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:path="path" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ // DeepLink with leading slash in android:path.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:path="/path" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // DeepLink with no leading slash in android:pathPrefix.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPrefix="pathPrefix" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ // DeepLink with leading slash in android:pathPrefix.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPrefix="/pathPrefix" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // DeepLink with no leading slash in android:pathPattern.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPattern="pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ // DeepLink with leading slash in android:pathPattern.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPattern="/pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // DeepLink with '.' start in pathPattern.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPattern=".*\\.pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // DeepLink with '*' start in pathPattern.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPattern="*" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+}
} // namespace aapt