Merge "Simplify UIDs in PacakgeInstaller app" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8b85a0962..5ead3e1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4354,7 +4354,7 @@
field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
field public static final int MATCH_ANY_USER = 4194304; // 0x400000
- field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
+ field @Deprecated public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
field @FlaggedApi("android.content.pm.fix_duplicated_flags") public static final long MATCH_CLONE_PROFILE_LONG = 17179869184L; // 0x400000000L
field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ae5cacd..fa9346e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -712,16 +712,22 @@
stopped = false;
hideForNow = false;
activityConfigCallback = new ViewRootImpl.ActivityConfigCallback() {
+
@Override
- public void onConfigurationChanged(Configuration overrideConfig,
- int newDisplayId) {
+ public void onConfigurationChanged(@NonNull Configuration overrideConfig,
+ int newDisplayId, @Nullable ActivityWindowInfo activityWindowInfo) {
if (activity == null) {
throw new IllegalStateException(
"Received config update for non-existing activity");
}
+ if (activityWindowInfoFlag() && activityWindowInfo == null) {
+ Log.w(TAG, "Received empty ActivityWindowInfo update for r=" + activity);
+ activityWindowInfo = mActivityWindowInfo;
+ }
activity.mMainThread.handleActivityConfigurationChanged(
ActivityClientRecord.this, overrideConfig, newDisplayId,
- mActivityWindowInfo, false /* alwaysReportChange */);
+ activityWindowInfo,
+ false /* alwaysReportChange */);
}
@Override
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 24cb9ea..cac10f5 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -487,6 +487,15 @@
*/
public static final int SUBREASON_FREEZER_BINDER_ASYNC_FULL = 31;
+ /**
+ * The process was killed because it was sending too many broadcasts while it is in the
+ * Cached state. This would be set only when the reason is {@link #REASON_OTHER}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_EXCESSIVE_OUTGOING_BROADCASTS_WHILE_CACHED = 32;
+
// If there is any OEM code which involves additional app kill reasons, it should
// be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
@@ -665,6 +674,7 @@
SUBREASON_EXCESSIVE_BINDER_OBJECTS,
SUBREASON_OOM_KILL,
SUBREASON_FREEZER_BINDER_ASYNC_FULL,
+ SUBREASON_EXCESSIVE_OUTGOING_BROADCASTS_WHILE_CACHED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SubReason {}
@@ -1396,6 +1406,8 @@
return "OOM KILL";
case SUBREASON_FREEZER_BINDER_ASYNC_FULL:
return "FREEZER BINDER ASYNC FULL";
+ case SUBREASON_EXCESSIVE_OUTGOING_BROADCASTS_WHILE_CACHED:
+ return "EXCESSIVE_OUTGOING_BROADCASTS_WHILE_CACHED";
default:
return "UNKNOWN";
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 6f6e091..716dee4 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -344,23 +344,37 @@
*/
private boolean mOwnsToken = false;
- private final Object mDirsLock = new Object();
- @GuardedBy("mDirsLock")
+ private final Object mDatabasesDirLock = new Object();
+ @GuardedBy("mDatabasesDirLock")
private File mDatabasesDir;
- @GuardedBy("mDirsLock")
+
+ private final Object mPreferencesDirLock = new Object();
@UnsupportedAppUsage
+ @GuardedBy("mPreferencesDirLock")
private File mPreferencesDir;
- @GuardedBy("mDirsLock")
+
+ private final Object mFilesDirLock = new Object();
+ @GuardedBy("mFilesDirLock")
private File mFilesDir;
- @GuardedBy("mDirsLock")
+
+ private final Object mCratesDirLock = new Object();
+ @GuardedBy("mCratesDirLock")
private File mCratesDir;
- @GuardedBy("mDirsLock")
+
+ private final Object mNoBackupFilesDirLock = new Object();
+ @GuardedBy("mNoBackupFilesDirLock")
private File mNoBackupFilesDir;
- @GuardedBy("mDirsLock")
+
+ private final Object mCacheDirLock = new Object();
+ @GuardedBy("mCacheDirLock")
private File mCacheDir;
- @GuardedBy("mDirsLock")
+
+ private final Object mCodeCacheDirLock = new Object();
+ @GuardedBy("mCodeCacheDirLock")
private File mCodeCacheDir;
+ private final Object mMiscDirsLock = new Object();
+
// The system service cache for the system services that are cached per-ContextImpl.
@UnsupportedAppUsage
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
@@ -742,7 +756,7 @@
@UnsupportedAppUsage
private File getPreferencesDir() {
- synchronized (mDirsLock) {
+ synchronized (mPreferencesDirLock) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDir(), "shared_prefs");
}
@@ -831,7 +845,7 @@
@Override
public File getFilesDir() {
- synchronized (mDirsLock) {
+ synchronized (mFilesDirLock) {
if (mFilesDir == null) {
mFilesDir = new File(getDataDir(), "files");
}
@@ -846,7 +860,7 @@
final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
.toAbsolutePath().normalize();
- synchronized (mDirsLock) {
+ synchronized (mCratesDirLock) {
if (mCratesDir == null) {
mCratesDir = cratesRootPath.toFile();
}
@@ -859,7 +873,7 @@
@Override
public File getNoBackupFilesDir() {
- synchronized (mDirsLock) {
+ synchronized (mNoBackupFilesDirLock) {
if (mNoBackupFilesDir == null) {
mNoBackupFilesDir = new File(getDataDir(), "no_backup");
}
@@ -876,7 +890,7 @@
@Override
public File[] getExternalFilesDirs(String type) {
- synchronized (mDirsLock) {
+ synchronized (mMiscDirsLock) {
File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName());
if (type != null) {
dirs = Environment.buildPaths(dirs, type);
@@ -894,7 +908,7 @@
@Override
public File[] getObbDirs() {
- synchronized (mDirsLock) {
+ synchronized (mMiscDirsLock) {
File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
}
@@ -902,7 +916,7 @@
@Override
public File getCacheDir() {
- synchronized (mDirsLock) {
+ synchronized (mCacheDirLock) {
if (mCacheDir == null) {
mCacheDir = new File(getDataDir(), "cache");
}
@@ -912,7 +926,7 @@
@Override
public File getCodeCacheDir() {
- synchronized (mDirsLock) {
+ synchronized (mCodeCacheDirLock) {
if (mCodeCacheDir == null) {
mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
}
@@ -938,7 +952,7 @@
@Override
public File[] getExternalCacheDirs() {
- synchronized (mDirsLock) {
+ synchronized (mMiscDirsLock) {
File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName());
// We don't try to create cache directories in-process, because they need special
// setup for accurate quota tracking. This ensures the cache dirs are always
@@ -949,7 +963,7 @@
@Override
public File[] getExternalMediaDirs() {
- synchronized (mDirsLock) {
+ synchronized (mMiscDirsLock) {
File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
}
@@ -1051,7 +1065,7 @@
}
private File getDatabasesDir() {
- synchronized (mDirsLock) {
+ synchronized (mDatabasesDirLock) {
if (mDatabasesDir == null) {
if ("android".equals(getPackageName())) {
mDatabasesDir = new File("/data/system");
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a075ac5..60dffbd 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6545,8 +6545,10 @@
}
/**
- * Flag for {@link #wipeData(int)}: also erase the device's external
- * storage (such as SD cards).
+ * Flag for {@link #wipeData(int)}: also erase the device's adopted external storage (such as
+ * adopted SD cards).
+ * @see <a href="{@docRoot}about/versions/marshmallow/android-6.0.html#adoptable-storage">
+ * Adoptable Storage Devices</a>
*/
public static final int WIPE_EXTERNAL_STORAGE = 0x0001;
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 6eab363..30a1135 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -79,6 +79,11 @@
int getDevicePolicy(int policyType);
/**
+ * Returns whether the device has a valid microphone.
+ */
+ boolean hasCustomAudioInputSupport();
+
+ /**
* Closes the virtual device and frees all associated resources.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 97fa2ba..b9e9afe 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -17,7 +17,6 @@
package android.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
-import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
@@ -176,8 +175,7 @@
@FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
public boolean hasCustomAudioInputSupport() {
try {
- return mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO) == DEVICE_POLICY_CUSTOM;
- // TODO(b/291735254): also check for a custom audio injection mix for this device id.
+ return mVirtualDevice.hasCustomAudioInputSupport();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9f2f74b..b5809cf 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -895,7 +895,7 @@
GET_DISABLED_COMPONENTS,
GET_DISABLED_UNTIL_USED_COMPONENTS,
GET_UNINSTALLED_PACKAGES,
- MATCH_CLONE_PROFILE,
+ MATCH_CLONE_PROFILE_LONG,
MATCH_QUARANTINED_COMPONENTS,
})
@Retention(RetentionPolicy.SOURCE)
@@ -1235,10 +1235,11 @@
public static final int MATCH_DEBUG_TRIAGED_MISSING = MATCH_DIRECT_BOOT_AUTO;
/**
- * Use {@link #MATCH_CLONE_PROFILE_LONG} instead.
+ * @deprecated Use {@link #MATCH_CLONE_PROFILE_LONG} instead.
*
* @hide
*/
+ @Deprecated
@SystemApi
public static final int MATCH_CLONE_PROFILE = 0x20000000;
diff --git a/core/java/android/credentials/selection/IntentCreationResult.java b/core/java/android/credentials/selection/IntentCreationResult.java
new file mode 100644
index 0000000..189ff7b
--- /dev/null
+++ b/core/java/android/credentials/selection/IntentCreationResult.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 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.credentials.selection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+
+/**
+ * Result of creating a Credential Manager UI intent.
+ *
+ * @hide
+ */
+public final class IntentCreationResult {
+ @NonNull
+ private final Intent mIntent;
+ @Nullable
+ private final String mFallbackUiPackageName;
+ @Nullable
+ private final String mOemUiPackageName;
+ @NonNull
+ private final OemUiUsageStatus mOemUiUsageStatus;
+
+ private IntentCreationResult(@NonNull Intent intent, @Nullable String fallbackUiPackageName,
+ @Nullable String oemUiPackageName, OemUiUsageStatus oemUiUsageStatus) {
+ mIntent = intent;
+ mFallbackUiPackageName = fallbackUiPackageName;
+ mOemUiPackageName = oemUiPackageName;
+ mOemUiUsageStatus = oemUiUsageStatus;
+ }
+
+ /** Returns the UI intent. */
+ @NonNull
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Returns the result of attempting to use the config_oemCredentialManagerDialogComponent
+ * as the Credential Manager UI.
+ */
+ @NonNull
+ public OemUiUsageStatus getOemUiUsageStatus() {
+ return mOemUiUsageStatus;
+ }
+
+ /**
+ * Returns the package name of the ui component specified in
+ * config_fallbackCredentialManagerDialogComponent, or null if unspecified / not parsable
+ * successfully.
+ */
+ @Nullable
+ public String getFallbackUiPackageName() {
+ return mFallbackUiPackageName;
+ }
+
+ /**
+ * Returns the package name of the oem ui component specified in
+ * config_oemCredentialManagerDialogComponent, or null if unspecified / not parsable.
+ */
+ @Nullable
+ public String getOemUiPackageName() {
+ return mOemUiPackageName;
+ }
+
+ /**
+ * Result of attempting to use the config_oemCredentialManagerDialogComponent as the Credential
+ * Manager UI.
+ */
+ public enum OemUiUsageStatus {
+ UNKNOWN,
+ // Success: the UI specified in config_oemCredentialManagerDialogComponent was used to
+ // fulfill the request.
+ SUCCESS,
+ // The config value was not specified (e.g. left empty).
+ OEM_UI_CONFIG_NOT_SPECIFIED,
+ // The config value component was specified but not found (e.g. component doesn't exist or
+ // component isn't a system app).
+ OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND,
+ // The config value component was found but not enabled.
+ OEM_UI_CONFIG_SPECIFIED_FOUND_BUT_NOT_ENABLED,
+ }
+
+ /**
+ * Builder for {@link IntentCreationResult}.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @NonNull
+ private Intent mIntent;
+ @Nullable
+ private String mFallbackUiPackageName = null;
+ @Nullable
+ private String mOemUiPackageName = null;
+ @NonNull
+ private OemUiUsageStatus mOemUiUsageStatus = OemUiUsageStatus.UNKNOWN;
+
+ public Builder(Intent intent) {
+ mIntent = intent;
+ }
+
+ /**
+ * Sets the package name of the ui component specified in
+ * config_fallbackCredentialManagerDialogComponent, or null if unspecified / not parsable
+ * successfully.
+ */
+ @NonNull
+ public Builder setFallbackUiPackageName(@Nullable String fallbackUiPackageName) {
+ mFallbackUiPackageName = fallbackUiPackageName;
+ return this;
+ }
+
+ /**
+ * Sets the package name of the oem ui component specified in
+ * config_oemCredentialManagerDialogComponent, or null if unspecified / not parsable.
+ */
+ @NonNull
+ public Builder setOemUiPackageName(@Nullable String oemUiPackageName) {
+ mOemUiPackageName = oemUiPackageName;
+ return this;
+ }
+
+ /**
+ * Sets the result of attempting to use the config_oemCredentialManagerDialogComponent
+ * as the Credential Manager UI.
+ */
+ @NonNull
+ public Builder setOemUiUsageStatus(OemUiUsageStatus oemUiUsageStatus) {
+ mOemUiUsageStatus = oemUiUsageStatus;
+ return this;
+ }
+
+ /** Builds a {@link IntentCreationResult}. */
+ @NonNull
+ public IntentCreationResult build() {
+ return new IntentCreationResult(mIntent, mFallbackUiPackageName, mOemUiPackageName,
+ mOemUiUsageStatus);
+ }
+ }
+}
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index 79fba9b..b98a0d8 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -36,6 +36,8 @@
import android.text.TextUtils;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
/**
@@ -57,98 +59,48 @@
* @hide
*/
@NonNull
- public static Intent createCredentialSelectorIntentForAutofill(
+ public static IntentCreationResult createCredentialSelectorIntentForAutofill(
@NonNull Context context,
@NonNull RequestInfo requestInfo,
@SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
@NonNull
ArrayList<DisabledProviderData> disabledProviderDataList,
@NonNull ResultReceiver resultReceiver) {
- return createCredentialSelectorIntent(context, requestInfo,
+ return createCredentialSelectorIntentInternal(context, requestInfo,
disabledProviderDataList, resultReceiver);
}
/**
* Generate a new launch intent to the Credential Selector UI.
+ *
+ * @param context the CredentialManager system service (only expected caller)
+ * context that may be used to query existence of the key UI
+ * application
+ * @param disabledProviderDataList the list of disabled provider data that when non-empty the
+ * UI should accordingly generate an entry suggesting the user
+ * to navigate to settings and enable them
+ * @param enabledProviderDataList the list of enabled provider that contain options for this
+ * request; the UI should render each option to the user for
+ * selection
+ * @param requestInfo the display information about the given app request
+ * @param resultReceiver used by the UI to send the UI selection result back
+ * @hide
*/
@NonNull
- private static Intent createCredentialSelectorIntent(
+ public static IntentCreationResult createCredentialSelectorIntentForCredMan(
@NonNull Context context,
@NonNull RequestInfo requestInfo,
@SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
@NonNull
+ ArrayList<ProviderData> enabledProviderDataList,
+ @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+ @NonNull
ArrayList<DisabledProviderData> disabledProviderDataList,
@NonNull ResultReceiver resultReceiver) {
- Intent intent = new Intent();
- setCredentialSelectorUiComponentName(context, intent);
- intent.putParcelableArrayListExtra(
- ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
- intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
- intent.putExtra(
- Constants.EXTRA_RESULT_RECEIVER, toIpcFriendlyResultReceiver(resultReceiver));
-
- return intent;
- }
-
- private static void setCredentialSelectorUiComponentName(@NonNull Context context,
- @NonNull Intent intent) {
- if (configurableSelectorUiEnabled()) {
- ComponentName componentName = getOemOverrideComponentName(context);
- if (componentName == null) {
- componentName = ComponentName.unflattenFromString(Resources.getSystem().getString(
- com.android.internal.R.string
- .config_fallbackCredentialManagerDialogComponent));
- }
- intent.setComponent(componentName);
- } else {
- ComponentName componentName = ComponentName.unflattenFromString(Resources.getSystem()
- .getString(com.android.internal.R.string
- .config_fallbackCredentialManagerDialogComponent));
- intent.setComponent(componentName);
- }
- }
-
- /**
- * Returns null if there is not an enabled and valid oem override component. It means the
- * default platform UI component name should be used instead.
- */
- @Nullable
- private static ComponentName getOemOverrideComponentName(@NonNull Context context) {
- ComponentName result = null;
- String oemComponentString =
- Resources.getSystem()
- .getString(
- com.android.internal.R.string
- .config_oemCredentialManagerDialogComponent);
- if (!TextUtils.isEmpty(oemComponentString)) {
- ComponentName oemComponentName = ComponentName.unflattenFromString(
- oemComponentString);
- if (oemComponentName != null) {
- try {
- ActivityInfo info = context.getPackageManager().getActivityInfo(
- oemComponentName,
- PackageManager.ComponentInfoFlags.of(
- PackageManager.MATCH_SYSTEM_ONLY));
- if (info.enabled && info.exported) {
- Slog.i(TAG,
- "Found enabled oem CredMan UI component."
- + oemComponentString);
- result = oemComponentName;
- } else {
- Slog.i(TAG,
- "Found enabled oem CredMan UI component but it was not "
- + "enabled.");
- }
- } catch (PackageManager.NameNotFoundException e) {
- Slog.i(TAG, "Unable to find oem CredMan UI component: "
- + oemComponentString + ".");
- }
- } else {
- Slog.i(TAG, "Invalid OEM ComponentName format.");
- }
- } else {
- Slog.i(TAG, "Invalid empty OEM component name.");
- }
+ IntentCreationResult result = createCredentialSelectorIntentInternal(context, requestInfo,
+ disabledProviderDataList, resultReceiver);
+ result.getIntent().putParcelableArrayListExtra(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
return result;
}
@@ -167,6 +119,7 @@
* @param requestInfo the display information about the given app request
* @param resultReceiver used by the UI to send the UI selection result back
*/
+ @VisibleForTesting
@NonNull
public static Intent createCredentialSelectorIntent(
@NonNull Context context,
@@ -178,22 +131,21 @@
@NonNull
ArrayList<DisabledProviderData> disabledProviderDataList,
@NonNull ResultReceiver resultReceiver) {
- Intent intent = createCredentialSelectorIntent(context, requestInfo,
- disabledProviderDataList, resultReceiver);
- intent.putParcelableArrayListExtra(
- ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
- return intent;
+ return createCredentialSelectorIntentForCredMan(context, requestInfo,
+ enabledProviderDataList, disabledProviderDataList, resultReceiver).getIntent();
}
/**
* Creates an Intent that cancels any UI matching the given request token id.
*/
+ @VisibleForTesting
@NonNull
public static Intent createCancelUiIntent(@NonNull Context context,
@NonNull IBinder requestToken, boolean shouldShowCancellationUi,
@NonNull String appPackageName) {
Intent intent = new Intent();
- setCredentialSelectorUiComponentName(context, intent);
+ IntentCreationResult.Builder intentResultBuilder = new IntentCreationResult.Builder(intent);
+ setCredentialSelectorUiComponentName(context, intent, intentResultBuilder);
intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi,
appPackageName));
@@ -201,6 +153,119 @@
}
/**
+ * Generate a new launch intent to the Credential Selector UI.
+ */
+ @NonNull
+ private static IntentCreationResult createCredentialSelectorIntentInternal(
+ @NonNull Context context,
+ @NonNull RequestInfo requestInfo,
+ @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+ @NonNull
+ ArrayList<DisabledProviderData> disabledProviderDataList,
+ @NonNull ResultReceiver resultReceiver) {
+ Intent intent = new Intent();
+ IntentCreationResult.Builder intentResultBuilder = new IntentCreationResult.Builder(intent);
+ setCredentialSelectorUiComponentName(context, intent, intentResultBuilder);
+ intent.putParcelableArrayListExtra(
+ ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
+ intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
+ intent.putExtra(
+ Constants.EXTRA_RESULT_RECEIVER, toIpcFriendlyResultReceiver(resultReceiver));
+ return intentResultBuilder.build();
+ }
+
+ private static void setCredentialSelectorUiComponentName(@NonNull Context context,
+ @NonNull Intent intent, @NonNull IntentCreationResult.Builder intentResultBuilder) {
+ if (configurableSelectorUiEnabled()) {
+ ComponentName componentName = getOemOverrideComponentName(context, intentResultBuilder);
+
+ ComponentName fallbackUiComponentName = null;
+ try {
+ fallbackUiComponentName = ComponentName.unflattenFromString(
+ Resources.getSystem().getString(
+ com.android.internal.R.string
+ .config_fallbackCredentialManagerDialogComponent));
+ intentResultBuilder.setFallbackUiPackageName(
+ fallbackUiComponentName.getPackageName());
+ } catch (Exception e) {
+ Slog.w(TAG, "Fallback CredMan IU not found: " + e);
+ }
+
+ if (componentName == null) {
+ componentName = fallbackUiComponentName;
+ }
+
+ intent.setComponent(componentName);
+ } else {
+ ComponentName componentName = ComponentName.unflattenFromString(Resources.getSystem()
+ .getString(com.android.internal.R.string
+ .config_fallbackCredentialManagerDialogComponent));
+ intent.setComponent(componentName);
+ }
+ }
+
+ /**
+ * Returns null if there is not an enabled and valid oem override component. It means the
+ * default platform UI component name should be used instead.
+ */
+ @Nullable
+ private static ComponentName getOemOverrideComponentName(@NonNull Context context,
+ @NonNull IntentCreationResult.Builder intentResultBuilder) {
+ ComponentName result = null;
+ String oemComponentString =
+ Resources.getSystem()
+ .getString(
+ com.android.internal.R.string
+ .config_oemCredentialManagerDialogComponent);
+ if (!TextUtils.isEmpty(oemComponentString)) {
+ ComponentName oemComponentName = null;
+ try {
+ oemComponentName = ComponentName.unflattenFromString(
+ oemComponentString);
+ } catch (Exception e) {
+ Slog.i(TAG, "Failed to parse OEM component name " + oemComponentString + ": " + e);
+ }
+ if (oemComponentName != null) {
+ try {
+ intentResultBuilder.setOemUiPackageName(oemComponentName.getPackageName());
+ ActivityInfo info = context.getPackageManager().getActivityInfo(
+ oemComponentName,
+ PackageManager.ComponentInfoFlags.of(
+ PackageManager.MATCH_SYSTEM_ONLY));
+ if (info.enabled && info.exported) {
+ intentResultBuilder.setOemUiUsageStatus(IntentCreationResult
+ .OemUiUsageStatus.SUCCESS);
+ Slog.i(TAG,
+ "Found enabled oem CredMan UI component."
+ + oemComponentString);
+ result = oemComponentName;
+ } else {
+ intentResultBuilder.setOemUiUsageStatus(IntentCreationResult
+ .OemUiUsageStatus.OEM_UI_CONFIG_SPECIFIED_FOUND_BUT_NOT_ENABLED);
+ Slog.i(TAG,
+ "Found enabled oem CredMan UI component but it was not "
+ + "enabled.");
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ intentResultBuilder.setOemUiUsageStatus(IntentCreationResult.OemUiUsageStatus
+ .OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND);
+ Slog.i(TAG, "Unable to find oem CredMan UI component: "
+ + oemComponentString + ".");
+ }
+ } else {
+ intentResultBuilder.setOemUiUsageStatus(IntentCreationResult.OemUiUsageStatus
+ .OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND);
+ Slog.i(TAG, "Invalid OEM ComponentName format.");
+ }
+ } else {
+ intentResultBuilder.setOemUiUsageStatus(
+ IntentCreationResult.OemUiUsageStatus.OEM_UI_CONFIG_NOT_SPECIFIED);
+ Slog.i(TAG, "Invalid empty OEM component name.");
+ }
+ return result;
+ }
+
+ /**
* Convert an instance of a "locally-defined" ResultReceiver to an instance of {@link
* android.os.ResultReceiver} itself, which the receiving process will be able to unmarshall.
*/
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 387eebe..ed4037c 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -18,6 +18,7 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -31,6 +32,8 @@
import com.android.internal.annotations.VisibleForTesting;
import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -53,6 +56,53 @@
@VisibleForTesting
static final int FLAG_ALLOW_FDS = 1 << 10;
+ @VisibleForTesting
+ static final int FLAG_HAS_BINDERS_KNOWN = 1 << 11;
+
+ @VisibleForTesting
+ static final int FLAG_HAS_BINDERS = 1 << 12;
+
+
+ /**
+ * Status when the Bundle can <b>assert</b> that the underlying Parcel DOES NOT contain
+ * Binder object(s).
+ *
+ * @hide
+ */
+ public static final int STATUS_BINDERS_NOT_PRESENT = 0;
+
+ /**
+ * Status when the Bundle can <b>assert</b> that there are Binder object(s) in the Parcel.
+ *
+ * @hide
+ */
+ public static final int STATUS_BINDERS_PRESENT = 1;
+
+ /**
+ * Status when the Bundle cannot be checked for Binders and there is no parcelled data
+ * available to check either.
+ * <p> This could happen when a Bundle is unparcelled or was never parcelled, and modified such
+ * that it is not possible to assert if the Bundle has any Binder objects in the current state.
+ *
+ * For e.g. calling {@link #putParcelable} or {@link #putBinder} could have added a Binder
+ * object to the Bundle but it is not possible to assert this fact unless the Bundle is written
+ * to a Parcel.
+ * </p>
+ *
+ * @hide
+ */
+ public static final int STATUS_BINDERS_UNKNOWN = 2;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = {"STATUS_BINDERS_"}, value = {
+ STATUS_BINDERS_PRESENT,
+ STATUS_BINDERS_UNKNOWN,
+ STATUS_BINDERS_NOT_PRESENT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HasBinderStatus {
+ }
+
/** An unmodifiable {@code Bundle} that is always {@link #isEmpty() empty}. */
public static final Bundle EMPTY;
@@ -75,7 +125,7 @@
*/
public Bundle() {
super();
- mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
+ mFlags = FLAG_HAS_FDS_KNOWN | FLAG_HAS_BINDERS_KNOWN | FLAG_ALLOW_FDS;
}
/**
@@ -111,7 +161,6 @@
*
* @param from The bundle to be copied.
* @param deep Whether is a deep or shallow copy.
- *
* @hide
*/
Bundle(Bundle from, boolean deep) {
@@ -143,7 +192,7 @@
*/
public Bundle(ClassLoader loader) {
super(loader);
- mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
+ mFlags = FLAG_HAS_FDS_KNOWN | FLAG_HAS_BINDERS_KNOWN | FLAG_ALLOW_FDS;
}
/**
@@ -154,7 +203,7 @@
*/
public Bundle(int capacity) {
super(capacity);
- mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
+ mFlags = FLAG_HAS_FDS_KNOWN | FLAG_HAS_BINDERS_KNOWN | FLAG_ALLOW_FDS;
}
/**
@@ -180,7 +229,7 @@
*/
public Bundle(PersistableBundle b) {
super(b);
- mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
+ mFlags = FLAG_HAS_FDS_KNOWN | FLAG_HAS_BINDERS_KNOWN | FLAG_ALLOW_FDS;
}
/**
@@ -292,6 +341,9 @@
if ((mFlags & FLAG_HAS_FDS) != 0) {
mFlags &= ~FLAG_HAS_FDS_KNOWN;
}
+ if ((mFlags & FLAG_HAS_BINDERS) != 0) {
+ mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
+ }
}
/**
@@ -306,13 +358,20 @@
bundle.mOwnsLazyValues = false;
mMap.putAll(bundle.mMap);
- // FD state is now known if and only if both bundles already knew
+ // FD and Binders state is now known if and only if both bundles already knew
if ((bundle.mFlags & FLAG_HAS_FDS) != 0) {
mFlags |= FLAG_HAS_FDS;
}
if ((bundle.mFlags & FLAG_HAS_FDS_KNOWN) == 0) {
mFlags &= ~FLAG_HAS_FDS_KNOWN;
}
+
+ if ((bundle.mFlags & FLAG_HAS_BINDERS) != 0) {
+ mFlags |= FLAG_HAS_BINDERS;
+ }
+ if ((bundle.mFlags & FLAG_HAS_BINDERS_KNOWN) == 0) {
+ mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
+ }
}
/**
@@ -343,6 +402,33 @@
return (mFlags & FLAG_HAS_FDS) != 0;
}
+ /**
+ * Returns a status indicating whether the bundle contains any parcelled Binder objects.
+ * @hide
+ */
+ public @HasBinderStatus int hasBinders() {
+ if ((mFlags & FLAG_HAS_BINDERS_KNOWN) != 0) {
+ if ((mFlags & FLAG_HAS_BINDERS) != 0) {
+ return STATUS_BINDERS_PRESENT;
+ } else {
+ return STATUS_BINDERS_NOT_PRESENT;
+ }
+ }
+
+ final Parcel p = mParcelledData;
+ if (p == null) {
+ return STATUS_BINDERS_UNKNOWN;
+ }
+ if (p.hasBinders()) {
+ mFlags = mFlags | FLAG_HAS_BINDERS | FLAG_HAS_BINDERS_KNOWN;
+ return STATUS_BINDERS_PRESENT;
+ } else {
+ mFlags = mFlags & ~FLAG_HAS_BINDERS;
+ mFlags |= FLAG_HAS_BINDERS_KNOWN;
+ return STATUS_BINDERS_NOT_PRESENT;
+ }
+ }
+
/** {@hide} */
@Override
public void putObject(@Nullable String key, @Nullable Object value) {
@@ -464,6 +550,7 @@
unparcel();
mMap.put(key, value);
mFlags &= ~FLAG_HAS_FDS_KNOWN;
+ mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
}
/**
@@ -502,6 +589,7 @@
unparcel();
mMap.put(key, value);
mFlags &= ~FLAG_HAS_FDS_KNOWN;
+ mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
}
/**
@@ -517,6 +605,7 @@
unparcel();
mMap.put(key, value);
mFlags &= ~FLAG_HAS_FDS_KNOWN;
+ mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
}
/** {@hide} */
@@ -525,6 +614,7 @@
unparcel();
mMap.put(key, value);
mFlags &= ~FLAG_HAS_FDS_KNOWN;
+ mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
}
/**
@@ -540,6 +630,7 @@
unparcel();
mMap.put(key, value);
mFlags &= ~FLAG_HAS_FDS_KNOWN;
+ mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
}
/**
@@ -680,6 +771,7 @@
public void putBinder(@Nullable String key, @Nullable IBinder value) {
unparcel();
mMap.put(key, value);
+ mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
}
/**
@@ -697,6 +789,7 @@
public void putIBinder(@Nullable String key, @Nullable IBinder value) {
unparcel();
mMap.put(key, value);
+ mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
}
/**
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index ccfb632..bcef815 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -475,6 +475,10 @@
private static native boolean nativeHasFileDescriptors(long nativePtr);
private static native boolean nativeHasFileDescriptorsInRange(
long nativePtr, int offset, int length);
+
+ private static native boolean nativeHasBinders(long nativePtr);
+ private static native boolean nativeHasBindersInRange(
+ long nativePtr, int offset, int length);
@RavenwoodThrow
private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
@RavenwoodThrow
@@ -970,6 +974,34 @@
}
/**
+ * Report whether the parcel contains any marshalled IBinder objects.
+ *
+ * @throws UnsupportedOperationException if binder kernel driver was disabled or if method was
+ * invoked in case of Binder RPC protocol.
+ * @hide
+ */
+ public boolean hasBinders() {
+ return nativeHasBinders(mNativePtr);
+ }
+
+ /**
+ * Report whether the parcel contains any marshalled {@link IBinder} objects in the range
+ * defined by {@code offset} and {@code length}.
+ *
+ * @param offset The offset from which the range starts. Should be between 0 and
+ * {@link #dataSize()}.
+ * @param length The length of the range. Should be between 0 and {@link #dataSize()} - {@code
+ * offset}.
+ * @return whether there are binders in the range or not.
+ * @throws IllegalArgumentException if the parameters are out of the permitted ranges.
+ *
+ * @hide
+ */
+ public boolean hasBinders(int offset, int length) {
+ return nativeHasBindersInRange(mNativePtr, offset, length);
+ }
+
+ /**
* Store or read an IBinder interface token in the parcel at the current
* {@link #dataPosition}. This is used to validate that the marshalled
* transaction is intended for the target interface. This is typically written
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index aad2b4e..25c2b0e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12386,6 +12386,13 @@
*/
public static final String HIDE_PRIVATESPACE_ENTRY_POINT = "hide_privatespace_entry_point";
+ /**
+ * Whether or not secure windows should be disabled. This only works on debuggable builds.
+ *
+ * @hide
+ */
+ public static final String DISABLE_SECURE_WINDOWS = "disable_secure_windows";
+
/** @hide */
public static final int PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK = 0;
/** @hide */
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
index d72441f..00236df 100644
--- a/core/java/android/service/chooser/flags.aconfig
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -27,14 +27,3 @@
description: "Provides additional callbacks with information about user actions in ChooserResult"
bug: "263474465"
}
-
-flag {
- name: "legacy_chooser_pinning_removal"
- namespace: "intentresolver"
- description: "Removing pinning functionality from the legacy chooser (used by partial screenshare)"
- bug: "301068735"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 41bfb24..3db45e0 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8496,8 +8496,9 @@
* hierarchy
* @param refocus when propagate is true, specifies whether to request the
* root view place new focus
+ * @hide
*/
- void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
+ public void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
mPrivateFlags &= ~PFLAG_FOCUSED;
clearParentsWantFocus();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 304e43e..c7eaf0d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -114,6 +114,7 @@
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
import static com.android.input.flags.Flags.enablePointerChoreographer;
+import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
import android.Manifest;
@@ -233,6 +234,7 @@
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
+import android.window.ActivityWindowInfo;
import android.window.BackEvent;
import android.window.ClientWindowFrames;
import android.window.CompatOnBackInvokedCallback;
@@ -435,13 +437,27 @@
* Callback for notifying activities.
*/
public interface ActivityConfigCallback {
-
/**
* Notifies about override config change and/or move to different display.
* @param overrideConfig New override config to apply to activity.
* @param newDisplayId New display id, {@link Display#INVALID_DISPLAY} if not changed.
*/
- void onConfigurationChanged(Configuration overrideConfig, int newDisplayId);
+ default void onConfigurationChanged(@NonNull Configuration overrideConfig,
+ int newDisplayId) {
+ // Must override one of the #onConfigurationChanged.
+ throw new IllegalStateException("Not implemented");
+ }
+
+ /**
+ * Notifies about override config change and/or move to different display.
+ * @param overrideConfig New override config to apply to activity.
+ * @param newDisplayId New display id, {@link Display#INVALID_DISPLAY} if not changed.
+ * @param activityWindowInfo New ActivityWindowInfo to apply to activity.
+ */
+ default void onConfigurationChanged(@NonNull Configuration overrideConfig,
+ int newDisplayId, @Nullable ActivityWindowInfo activityWindowInfo) {
+ onConfigurationChanged(overrideConfig, newDisplayId);
+ }
/**
* Notify the corresponding activity about the request to show or hide a camera compat
@@ -467,7 +483,7 @@
* In that case we receive a call back from {@link ActivityThread} and this flag is used to
* preserve the initial value.
*
- * @see #performConfigurationChange(MergedConfiguration, boolean, int)
+ * @see #performConfigurationChange
*/
private boolean mForceNextConfigUpdate;
@@ -814,6 +830,13 @@
/** Configurations waiting to be applied. */
private final MergedConfiguration mPendingMergedConfiguration = new MergedConfiguration();
+ /** Non-{@code null} if {@link #mActivityConfigCallback} is not {@code null}. */
+ @Nullable
+ private ActivityWindowInfo mPendingActivityWindowInfo;
+ /** Non-{@code null} if {@link #mActivityConfigCallback} is not {@code null}. */
+ @Nullable
+ private ActivityWindowInfo mLastReportedActivityWindowInfo;
+
boolean mScrollMayChange;
@SoftInputModeFlags
int mSoftInputMode;
@@ -1260,8 +1283,18 @@
* Add activity config callback to be notified about override config changes and camera
* compat control state updates.
*/
- public void setActivityConfigCallback(ActivityConfigCallback callback) {
+ public void setActivityConfigCallback(@Nullable ActivityConfigCallback callback) {
mActivityConfigCallback = callback;
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ if (callback == null) {
+ mPendingActivityWindowInfo = null;
+ mLastReportedActivityWindowInfo = null;
+ } else {
+ mPendingActivityWindowInfo = new ActivityWindowInfo();
+ mLastReportedActivityWindowInfo = new ActivityWindowInfo();
+ }
}
public void setOnContentApplyWindowInsetsListener(OnContentApplyWindowInsetsListener listener) {
@@ -2096,7 +2129,8 @@
/** Handles messages {@link #MSG_RESIZED} and {@link #MSG_RESIZED_REPORT}. */
private void handleResized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
- boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) {
+ boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing,
+ @Nullable ActivityWindowInfo activityWindowInfo) {
if (!mAdded) {
return;
}
@@ -2114,7 +2148,14 @@
mInsetsController.onStateChanged(insetsState);
final float compatScale = frames.compatScale;
final boolean frameChanged = !mWinFrame.equals(frame);
- final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration);
+ final boolean shouldReportActivityWindowInfoChanged =
+ // Can be null if callbacks is not set
+ mLastReportedActivityWindowInfo != null
+ // Can be null if not activity window
+ && activityWindowInfo != null
+ && !mLastReportedActivityWindowInfo.equals(activityWindowInfo);
+ final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration)
+ || shouldReportActivityWindowInfoChanged;
final boolean attachedFrameChanged =
!Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
final boolean displayChanged = mDisplay.getDisplayId() != displayId;
@@ -2133,7 +2174,8 @@
if (configChanged) {
// If configuration changed - notify about that and, maybe, about move to display.
performConfigurationChange(mergedConfiguration, false /* force */,
- displayChanged ? displayId : INVALID_DISPLAY /* same display */);
+ displayChanged ? displayId : INVALID_DISPLAY /* same display */,
+ activityWindowInfo);
} else if (displayChanged) {
// Moved to display without config change - report last applied one.
onMovedToDisplay(displayId, mLastConfigurationFromResources);
@@ -3532,12 +3574,18 @@
// WindowManagerService has reported back a frame from a configuration not yet
// handled by the client. In this case, we need to accept the configuration so we
// do not lay out and draw with the wrong configuration.
- if (mRelayoutRequested
- && !mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {
+ boolean shouldPerformConfigurationUpdate =
+ !mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)
+ || !Objects.equals(mPendingActivityWindowInfo,
+ mLastReportedActivityWindowInfo);
+ if (mRelayoutRequested && shouldPerformConfigurationUpdate) {
if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: "
+ mPendingMergedConfiguration.getMergedConfiguration());
performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration),
- !mFirst, INVALID_DISPLAY /* same display */);
+ !mFirst, INVALID_DISPLAY /* same display */,
+ mPendingActivityWindowInfo != null
+ ? new ActivityWindowInfo(mPendingActivityWindowInfo)
+ : null);
updatedConfiguration = true;
}
final boolean updateSurfaceNeeded = mUpdateSurfaceNeeded;
@@ -6063,9 +6111,11 @@
* @param force Flag indicating if we should force apply the config.
* @param newDisplayId Id of new display if moved, {@link Display#INVALID_DISPLAY} if not
* changed.
+ * @param activityWindowInfo New activity window info. {@code null} if it is non-app window, or
+ * this is not a Configuration change to the activity window (global).
*/
- private void performConfigurationChange(MergedConfiguration mergedConfiguration, boolean force,
- int newDisplayId) {
+ private void performConfigurationChange(@NonNull MergedConfiguration mergedConfiguration,
+ boolean force, int newDisplayId, @Nullable ActivityWindowInfo activityWindowInfo) {
if (mergedConfiguration == null) {
throw new IllegalArgumentException("No merged config provided.");
}
@@ -6105,6 +6155,9 @@
}
mLastReportedMergedConfiguration.setConfiguration(globalConfig, overrideConfig);
+ if (mLastReportedActivityWindowInfo != null && activityWindowInfo != null) {
+ mLastReportedActivityWindowInfo.set(activityWindowInfo);
+ }
mForceNextConfigUpdate = force;
if (mActivityConfigCallback != null) {
@@ -6112,7 +6165,8 @@
// This basically initiates a round trip to ActivityThread and back, which will ensure
// that corresponding activity and resources are updated before updating inner state of
// ViewRootImpl. Eventually it will call #updateConfiguration().
- mActivityConfigCallback.onConfigurationChanged(overrideConfig, newDisplayId);
+ mActivityConfigCallback.onConfigurationChanged(overrideConfig, newDisplayId,
+ activityWindowInfo);
} else {
// There is no activity callback - update the configuration right away.
updateConfiguration(newDisplayId);
@@ -6354,13 +6408,15 @@
final boolean reportDraw = msg.what == MSG_RESIZED_REPORT;
final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg2;
final InsetsState insetsState = (InsetsState) args.arg3;
+ final ActivityWindowInfo activityWindowInfo = (ActivityWindowInfo) args.arg4;
final boolean forceLayout = args.argi1 != 0;
final boolean alwaysConsumeSystemBars = args.argi2 != 0;
final int displayId = args.argi3;
final int syncSeqId = args.argi4;
final boolean dragResizing = args.argi5 != 0;
handleResized(frames, reportDraw, mergedConfiguration, insetsState, forceLayout,
- alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
+ alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing,
+ activityWindowInfo);
args.recycle();
break;
}
@@ -6504,7 +6560,8 @@
mLastReportedMergedConfiguration.getOverrideConfiguration());
performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration),
- false /* force */, INVALID_DISPLAY /* same display */);
+ false /* force */, INVALID_DISPLAY /* same display */,
+ mLastReportedActivityWindowInfo);
} break;
case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
setAccessibilityFocus(null, null);
@@ -8938,6 +8995,14 @@
if (maybeSyncSeqId > 0) {
mSyncSeqId = maybeSyncSeqId;
}
+ if (activityWindowInfoFlag() && mPendingActivityWindowInfo != null) {
+ final ActivityWindowInfo outInfo = mRelayoutBundle.getParcelable(
+ IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO,
+ ActivityWindowInfo.class);
+ if (outInfo != null) {
+ mPendingActivityWindowInfo.set(outInfo);
+ }
+ }
mWinFrameInScreen.set(mTmpFrames.frame);
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
@@ -9358,6 +9423,10 @@
+ mLastReportedMergedConfiguration);
writer.println(innerPrefix + "mLastConfigurationFromResources="
+ mLastConfigurationFromResources);
+ if (mLastReportedActivityWindowInfo != null) {
+ writer.println(innerPrefix + "mLastReportedActivityWindowInfo="
+ + mLastReportedActivityWindowInfo);
+ }
writer.println(innerPrefix + "mIsAmbientMode=" + mIsAmbientMode);
writer.println(innerPrefix + "mUnbufferedInputSource="
+ Integer.toHexString(mUnbufferedInputSource));
@@ -9571,12 +9640,14 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void dispatchResized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
- boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) {
+ boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing,
+ @Nullable ActivityWindowInfo activityWindowInfo) {
Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
SomeArgs args = SomeArgs.obtain();
args.arg1 = frames;
args.arg2 = mergedConfiguration;
args.arg3 = insetsState;
+ args.arg4 = activityWindowInfo;
args.argi1 = forceLayout ? 1 : 0;
args.argi2 = alwaysConsumeSystemBars ? 1 : 0;
args.argi3 = displayId;
@@ -11038,6 +11109,9 @@
if (viewAncestor == null) {
return;
}
+ // TODO(b/26595351): update WindowStateResizeItem
+ final ActivityWindowInfo activityWindowInfo = viewAncestor
+ .mLastReportedActivityWindowInfo;
if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#resized",
viewAncestor.getInsetsController().getHost().getInputMethodManager(),
@@ -11048,7 +11122,8 @@
if (isFromResizeItem && viewAncestor.mHandler.getLooper()
== ActivityThread.currentActivityThread().getLooper()) {
viewAncestor.handleResized(frames, reportDraw, mergedConfiguration, insetsState,
- forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
+ forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing,
+ activityWindowInfo);
return;
}
// The the parameters from WindowStateResizeItem are already copied.
@@ -11060,7 +11135,8 @@
mergedConfiguration = new MergedConfiguration(mergedConfiguration);
}
viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
- forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
+ forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing,
+ activityWindowInfo);
}
@Override
@@ -12716,7 +12792,10 @@
}
private boolean shouldEnableDvrr() {
- return sToolkitSetFrameRateReadOnlyFlagValue && mIsFrameRatePowerSavingsBalanced;
+ // uncomment this when we are ready for enabling dVRR
+ // return sToolkitSetFrameRateReadOnlyFlagValue && mIsFrameRatePowerSavingsBalanced;
+ return false;
+
}
private void checkIdleness() {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dc060ba..fbb5116 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9733,7 +9733,7 @@
return KEY_EVENT_HANDLED;
}
if (hasFocus()) {
- clearFocus();
+ clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
InputMethodManager imm = getInputMethodManager();
if (imm != null) {
imm.hideSoftInputFromView(this, 0);
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 2e80b7e..c70febb 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -20,7 +20,6 @@
import static android.view.accessibility.AccessibilityManager.ShortcutType;
import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
-import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.createEnableDialogContentView;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
import static com.android.internal.accessibility.util.AccessibilityUtils.isUserSetupCompleted;
@@ -115,39 +114,22 @@
private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) {
final AccessibilityTarget target = mTargets.get(position);
- if (Flags.cleanupAccessibilityWarningDialog()) {
- if (target instanceof AccessibilityServiceTarget serviceTarget) {
- if (sendRestrictedDialogIntentIfNeeded(target)) {
- return;
- }
- final AccessibilityManager am = getSystemService(AccessibilityManager.class);
- if (am.isAccessibilityServiceWarningRequired(
- serviceTarget.getAccessibilityServiceInfo())) {
- showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target,
- position, mTargetAdapter);
- return;
- }
+ if (target instanceof AccessibilityServiceTarget serviceTarget) {
+ if (sendRestrictedDialogIntentIfNeeded(target)) {
+ return;
}
- if (target instanceof AccessibilityActivityTarget activityTarget) {
- if (!activityTarget.isShortcutEnabled()
- && sendRestrictedDialogIntentIfNeeded(activityTarget)) {
- return;
- }
+ final AccessibilityManager am = getSystemService(AccessibilityManager.class);
+ if (am.isAccessibilityServiceWarningRequired(
+ serviceTarget.getAccessibilityServiceInfo())) {
+ showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target,
+ position, mTargetAdapter);
+ return;
}
- } else {
- if (!target.isShortcutEnabled()) {
- if (target instanceof AccessibilityServiceTarget
- || target instanceof AccessibilityActivityTarget) {
- if (sendRestrictedDialogIntentIfNeeded(target)) {
- return;
- }
- }
-
- if (target instanceof AccessibilityServiceTarget) {
- showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target,
- position, mTargetAdapter);
- return;
- }
+ }
+ if (target instanceof AccessibilityActivityTarget activityTarget) {
+ if (!activityTarget.isShortcutEnabled()
+ && sendRestrictedDialogIntentIfNeeded(activityTarget)) {
+ return;
}
}
@@ -178,37 +160,25 @@
return;
}
- if (Flags.cleanupAccessibilityWarningDialog()) {
- mPermissionDialog = AccessibilityServiceWarning
- .createAccessibilityServiceWarningDialog(context,
- serviceTarget.getAccessibilityServiceInfo(),
- v -> {
- serviceTarget.onCheckedChanged(true);
- targetAdapter.notifyDataSetChanged();
- mPermissionDialog.dismiss();
- }, v -> {
- serviceTarget.onCheckedChanged(false);
- mPermissionDialog.dismiss();
- },
- v -> {
- mTargets.remove(position);
- context.getPackageManager().getPackageInstaller().uninstall(
- serviceTarget.getComponentName().getPackageName(), null);
- targetAdapter.notifyDataSetChanged();
- mPermissionDialog.dismiss();
- });
- mPermissionDialog.setOnDismissListener(dialog -> mPermissionDialog = null);
- } else {
- mPermissionDialog = new AlertDialog.Builder(context)
- .setView(createEnableDialogContentView(context, serviceTarget,
- v -> {
- mPermissionDialog.dismiss();
- targetAdapter.notifyDataSetChanged();
- },
- v -> mPermissionDialog.dismiss()))
- .setOnDismissListener(dialog -> mPermissionDialog = null)
- .create();
- }
+ mPermissionDialog = AccessibilityServiceWarning
+ .createAccessibilityServiceWarningDialog(context,
+ serviceTarget.getAccessibilityServiceInfo(),
+ v -> {
+ serviceTarget.onCheckedChanged(true);
+ targetAdapter.notifyDataSetChanged();
+ mPermissionDialog.dismiss();
+ }, v -> {
+ serviceTarget.onCheckedChanged(false);
+ mPermissionDialog.dismiss();
+ },
+ v -> {
+ mTargets.remove(position);
+ context.getPackageManager().getPackageInstaller().uninstall(
+ serviceTarget.getComponentName().getPackageName(), null);
+ targetAdapter.notifyDataSetChanged();
+ mPermissionDialog.dismiss();
+ });
+ mPermissionDialog.setOnDismissListener(dialog -> mPermissionDialog = null);
mPermissionDialog.show();
}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index 3d3db47..0d82d63 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -37,14 +37,8 @@
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
-import android.text.BidiFormatter;
-import android.view.LayoutInflater;
-import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.ShortcutType;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -52,7 +46,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Locale;
/**
* Collection of utilities for accessibility target.
@@ -298,50 +291,6 @@
}
/**
- * @deprecated Use {@link AccessibilityServiceWarning}.
- */
- @Deprecated
- static View createEnableDialogContentView(Context context,
- AccessibilityServiceTarget target, View.OnClickListener allowListener,
- View.OnClickListener denyListener) {
- final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
-
- final View content = inflater.inflate(
- R.layout.accessibility_enable_service_warning, /* root= */ null);
-
- final ImageView dialogIcon = content.findViewById(
- R.id.accessibility_permissionDialog_icon);
- dialogIcon.setImageDrawable(target.getIcon());
-
- final TextView dialogTitle = content.findViewById(
- R.id.accessibility_permissionDialog_title);
- dialogTitle.setText(context.getString(R.string.accessibility_enable_service_title,
- getServiceName(context, target.getLabel())));
-
- final Button allowButton = content.findViewById(
- R.id.accessibility_permission_enable_allow_button);
- final Button denyButton = content.findViewById(
- R.id.accessibility_permission_enable_deny_button);
- allowButton.setOnClickListener((view) -> {
- target.onCheckedChanged(/* isChecked= */ true);
- allowListener.onClick(view);
- });
- denyButton.setOnClickListener((view) -> {
- target.onCheckedChanged(/* isChecked= */ false);
- denyListener.onClick(view);
- });
-
- return content;
- }
-
- // Gets the service name and bidi wrap it to protect from bidi side effects.
- private static CharSequence getServiceName(Context context, CharSequence label) {
- final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
- return BidiFormatter.getInstance(locale).unicodeWrap(label);
- }
-
- /**
* Determines if the{@link AccessibilityTarget} is allowed.
*/
public static boolean isAccessibilityTargetAllowed(Context context, String packageName,
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 29669d3..ab456a8 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -96,7 +96,6 @@
import android.provider.OpenableColumns;
import android.provider.Settings;
import android.service.chooser.ChooserTarget;
-import android.service.chooser.Flags;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.HashedStringCache;
@@ -1801,54 +1800,6 @@
return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
}
- private void showTargetDetails(TargetInfo targetInfo) {
- if (targetInfo == null) return;
-
- ArrayList<DisplayResolveInfo> targetList;
- ChooserTargetActionsDialogFragment fragment = new ChooserTargetActionsDialogFragment();
- Bundle bundle = new Bundle();
-
- if (targetInfo instanceof SelectableTargetInfo) {
- SelectableTargetInfo selectableTargetInfo = (SelectableTargetInfo) targetInfo;
- if (selectableTargetInfo.getDisplayResolveInfo() == null
- || selectableTargetInfo.getChooserTarget() == null) {
- Log.e(TAG, "displayResolveInfo or chooserTarget in selectableTargetInfo are null");
- return;
- }
- targetList = new ArrayList<>();
- targetList.add(selectableTargetInfo.getDisplayResolveInfo());
- bundle.putString(ChooserTargetActionsDialogFragment.SHORTCUT_ID_KEY,
- selectableTargetInfo.getChooserTarget().getIntentExtras().getString(
- Intent.EXTRA_SHORTCUT_ID));
- bundle.putBoolean(ChooserTargetActionsDialogFragment.IS_SHORTCUT_PINNED_KEY,
- selectableTargetInfo.isPinned());
- bundle.putParcelable(ChooserTargetActionsDialogFragment.INTENT_FILTER_KEY,
- getTargetIntentFilter());
- if (selectableTargetInfo.getDisplayLabel() != null) {
- bundle.putString(ChooserTargetActionsDialogFragment.SHORTCUT_TITLE_KEY,
- selectableTargetInfo.getDisplayLabel().toString());
- }
- } else if (targetInfo instanceof MultiDisplayResolveInfo) {
- // For multiple targets, include info on all targets
- MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
- targetList = mti.getTargets();
- } else {
- targetList = new ArrayList<DisplayResolveInfo>();
- targetList.add((DisplayResolveInfo) targetInfo);
- }
- // Adding userHandle from ResolveInfo allows the app icon in Dialog Box to be
- // resolved correctly.
- bundle.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
- getResolveInfoUserHandle(
- targetInfo.getResolveInfo(),
- mChooserMultiProfilePagerAdapter.getCurrentUserHandle()));
- bundle.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY,
- targetList);
- fragment.setArguments(bundle);
-
- fragment.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
- }
-
private void modifyTargetIntent(Intent in) {
if (isSendAction(in)) {
in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
@@ -2544,10 +2495,7 @@
@Override
public boolean isComponentPinned(ComponentName name) {
- if (Flags.legacyChooserPinningRemoval()) {
- return false;
- }
- return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
+ return false;
}
@Override
@@ -3135,34 +3083,10 @@
if (isClickable) {
itemView.setOnClickListener(v -> startSelected(mListPosition,
false/* always */, true/* filterd */));
-
- itemView.setOnLongClickListener(v -> {
- final TargetInfo ti = mChooserMultiProfilePagerAdapter.getActiveListAdapter()
- .targetInfoForPosition(mListPosition, /* filtered */ true);
-
- // This should always be the case for ItemViewHolder, check for validity
- if (ti instanceof DisplayResolveInfo && shouldShowTargetDetails(ti)) {
- showTargetDetails((DisplayResolveInfo) ti);
- }
- return true;
- });
}
}
}
- private boolean shouldShowTargetDetails(TargetInfo ti) {
- if (Flags.legacyChooserPinningRemoval()) {
- // Never show the long press menu if we've removed pinning.
- return false;
- }
- ComponentName nearbyShare = getNearbySharingComponent();
- // Suppress target details for nearby share to hide pin/unpin action
- boolean isNearbyShare = nearbyShare != null && nearbyShare.equals(
- ti.getResolvedComponentName()) && shouldNearbyShareBeFirstInRankedRow();
- return ti instanceof SelectableTargetInfo
- || (ti instanceof DisplayResolveInfo && !isNearbyShare);
- }
-
/**
* Add a footer to the list, to support scrolling behavior below the navbar.
*/
@@ -3517,16 +3441,6 @@
}
});
- // Show menu for both direct share and app share targets after long click.
- v.setOnLongClickListener(v1 -> {
- TargetInfo ti = mChooserListAdapter.targetInfoForPosition(
- holder.getItemIndex(column), true);
- if (shouldShowTargetDetails(ti)) {
- showTargetDetails(ti);
- }
- return true;
- });
-
holder.addView(i, v);
// Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 3662d69..d2a533c 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -124,10 +124,13 @@
public static final int CUJ_BACK_PANEL_ARROW = 88;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK = 89;
public static final int CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH = 90;
+ public static final int CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE = 91;
+ public static final int CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR = 92;
+ public static final int CUJ_LAUNCHER_SAVE_APP_PAIR = 93;
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
@VisibleForTesting
- static final int LAST_CUJ = CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH;
+ static final int LAST_CUJ = CUJ_LAUNCHER_SAVE_APP_PAIR;
/** @hide */
@IntDef({
@@ -212,6 +215,9 @@
CUJ_BACK_PANEL_ARROW,
CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK,
CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH,
+ CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE,
+ CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR,
+ CUJ_LAUNCHER_SAVE_APP_PAIR
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -306,6 +312,9 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BACK_PANEL_ARROW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BACK_PANEL_ARROW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_BACK;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_WEB_SEARCH;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SAVE_APP_PAIR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SAVE_APP_PAIR;
}
private Cuj() {
@@ -484,6 +493,12 @@
return "LAUNCHER_CLOSE_ALL_APPS_BACK";
case CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH:
return "LAUNCHER_SEARCH_QSB_WEB_SEARCH";
+ case CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE:
+ return "LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE";
+ case CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR:
+ return "LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR";
+ case CUJ_LAUNCHER_SAVE_APP_PAIR:
+ return "LAUNCHER_SAVE_APP_PAIR";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 0ec8b74..a288fb7 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -165,6 +165,9 @@
@Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
@Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = Cuj.CUJ_PREDICTIVE_BACK_CROSS_TASK;
@Deprecated public static final int CUJ_PREDICTIVE_BACK_HOME = Cuj.CUJ_PREDICTIVE_BACK_HOME;
+ @Deprecated public static final int CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE = Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
+ @Deprecated public static final int CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR = Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR;
+ @Deprecated public static final int CUJ_LAUNCHER_SAVE_APP_PAIR = Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR;
private static class InstanceHolder {
public static final InteractionJankMonitor INSTANCE =
diff --git a/core/java/com/android/internal/net/ConnectivityBlobStore.java b/core/java/com/android/internal/net/ConnectivityBlobStore.java
new file mode 100644
index 0000000..51997cf
--- /dev/null
+++ b/core/java/com/android/internal/net/ConnectivityBlobStore.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 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.net;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+
+/**
+ * Database for storing blobs with a key of name strings.
+ * @hide
+ */
+public class ConnectivityBlobStore {
+ private static final String TAG = ConnectivityBlobStore.class.getSimpleName();
+ private static final String TABLENAME = "blob_table";
+ private static final String ROOT_DIR = "/data/misc/connectivityblobdb/";
+
+ private static final String CREATE_TABLE =
+ "CREATE TABLE IF NOT EXISTS " + TABLENAME + " ("
+ + "owner INTEGER,"
+ + "name BLOB,"
+ + "blob BLOB,"
+ + "UNIQUE(owner, name));";
+
+ private final SQLiteDatabase mDb;
+
+ /**
+ * Construct a ConnectivityBlobStore object.
+ *
+ * @param dbName the filename of the database to create/access.
+ */
+ public ConnectivityBlobStore(String dbName) {
+ this(new File(ROOT_DIR + dbName));
+ }
+
+ @VisibleForTesting
+ public ConnectivityBlobStore(File file) {
+ final SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
+ .addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY)
+ .addOpenFlags(SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING)
+ .build();
+ mDb = SQLiteDatabase.openDatabase(file, params);
+ mDb.execSQL(CREATE_TABLE);
+ }
+}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index a22232a..f5b1a47 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -388,9 +388,9 @@
*/
void showMediaOutputSwitcher(String packageName);
- /** Enters desktop mode.
+ /** Enters desktop mode from the current focused app.
*
* @param displayId the id of the current display.
*/
- void enterDesktop(int displayId);
+ void moveFocusedTaskToDesktop(int displayId);
}
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index 01b4569..c07e624 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -278,11 +278,6 @@
// be ready to glue. This can only happen if the button is initialized and displayed and
// *then* someone calls glueIcon or glueLabel.
- if (mIconToGlue == null) {
- Log.w(TAG, "glueIconAndLabelIfNeeded: label glued without icon; doing nothing");
- return;
- }
-
if (mLabelToGlue == null) {
Log.w(TAG, "glueIconAndLabelIfNeeded: icon glued without label; doing nothing");
return;
@@ -318,6 +313,14 @@
private static final String POP_DIRECTIONAL_ISOLATE = "\u2069";
private void glueIconAndLabel(int layoutDirection) {
+ if (mIconToGlue == null) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "glueIconAndLabel: null icon, setting text to label");
+ }
+ setText(mLabelToGlue);
+ return;
+ }
+
final boolean rtlLayout = layoutDirection == LAYOUT_DIRECTION_RTL;
if (DEBUG_NEW_ACTION_LAYOUT) {
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 3539476..584ebaa 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -661,6 +661,35 @@
return;
}
+static jboolean android_os_Parcel_hasBinders(JNIEnv* env, jclass clazz, jlong nativePtr) {
+ Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+ if (parcel != NULL) {
+ bool result;
+ status_t err = parcel->hasBinders(&result);
+ if (err != NO_ERROR) {
+ signalExceptionForError(env, clazz, err);
+ return JNI_FALSE;
+ }
+ return result ? JNI_TRUE : JNI_FALSE;
+ }
+ return JNI_FALSE;
+}
+
+static jboolean android_os_Parcel_hasBindersInRange(JNIEnv* env, jclass clazz, jlong nativePtr,
+ jint offset, jint length) {
+ Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+ if (parcel != NULL) {
+ bool result;
+ status_t err = parcel->hasBindersInRange(offset, length, &result);
+ if (err != NO_ERROR) {
+ signalExceptionForError(env, clazz, err);
+ return JNI_FALSE;
+ }
+ return result ? JNI_TRUE : JNI_FALSE;
+ }
+ return JNI_FALSE;
+}
+
static jboolean android_os_Parcel_hasFileDescriptors(jlong nativePtr)
{
jboolean ret = JNI_FALSE;
@@ -806,7 +835,7 @@
}
// ----------------------------------------------------------------------------
-
+// clang-format off
static const JNINativeMethod gParcelMethods[] = {
// @CriticalNative
{"nativeMarkSensitive", "(J)V", (void*)android_os_Parcel_markSensitive},
@@ -886,6 +915,9 @@
// @CriticalNative
{"nativeHasFileDescriptors", "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
{"nativeHasFileDescriptorsInRange", "(JII)Z", (void*)android_os_Parcel_hasFileDescriptorsInRange},
+
+ {"nativeHasBinders", "(J)Z", (void*)android_os_Parcel_hasBinders},
+ {"nativeHasBindersInRange", "(JII)Z", (void*)android_os_Parcel_hasBindersInRange},
{"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
{"nativeEnforceInterface", "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
@@ -900,6 +932,7 @@
// @CriticalNative
{"nativeReplaceCallingWorkSourceUid", "(JI)Z", (void*)android_os_Parcel_replaceCallingWorkSourceUid},
};
+// clang-format on
const char* const kParcelPathName = "android/os/Parcel";
diff --git a/core/res/res/drawable/autofill_dataset_picker_background.xml b/core/res/res/drawable/autofill_dataset_picker_background.xml
index d574970..6c4ef11 100644
--- a/core/res/res/drawable/autofill_dataset_picker_background.xml
+++ b/core/res/res/drawable/autofill_dataset_picker_background.xml
@@ -16,7 +16,7 @@
<inset xmlns:android="http://schemas.android.com/apk/res/android">
<shape android:shape="rectangle">
- <corners android:radius="@dimen/config_bottomDialogCornerRadius" />
+ <corners android:radius="@dimen/config_buttonCornerRadius" />
<solid android:color="?attr/colorBackground" />
</shape>
</inset>
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index 93c2e0e..40e79ad 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.annotations.Presubmit;
import android.platform.test.ravenwood.RavenwoodRule;
@@ -445,6 +446,42 @@
assertThat(bundle.size()).isEqualTo(0);
}
+ @Test
+ @DisabledOnRavenwood(blockedBy = Parcel.class)
+ public void parcelledBundleWithBinder_shouldReturnHasBindersTrue() throws Exception {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("test", new CustomParcelable(13, "Tiramisu"));
+ bundle.putBinder("test_binder",
+ new IBinderWorkSourceNestedService.Stub() {
+
+ public int[] nestedCallWithWorkSourceToSet(int uidToBlame) {
+ return new int[0];
+ }
+
+ public int[] nestedCall() {
+ return new int[0];
+ }
+ });
+ Bundle bundle2 = new Bundle(getParcelledBundle(bundle));
+ assertEquals(bundle2.hasBinders(), Bundle.STATUS_BINDERS_PRESENT);
+
+ bundle2.putParcelable("test2", new CustomParcelable(13, "Tiramisu"));
+ assertEquals(bundle2.hasBinders(), Bundle.STATUS_BINDERS_UNKNOWN);
+ }
+
+ @Test
+ @DisabledOnRavenwood(blockedBy = Parcel.class)
+ public void parcelledBundleWithoutBinder_shouldReturnHasBindersFalse() throws Exception {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("test", new CustomParcelable(13, "Tiramisu"));
+ Bundle bundle2 = new Bundle(getParcelledBundle(bundle));
+ //Should fail to load with framework classloader.
+ assertEquals(bundle2.hasBinders(), Bundle.STATUS_BINDERS_NOT_PRESENT);
+
+ bundle2.putParcelable("test2", new CustomParcelable(13, "Tiramisu"));
+ assertEquals(bundle2.hasBinders(), Bundle.STATUS_BINDERS_UNKNOWN);
+ }
+
private Bundle getMalformedBundle() {
Parcel p = Parcel.obtain();
p.writeInt(BaseBundle.BUNDLE_MAGIC);
@@ -520,6 +557,7 @@
public CustomParcelable createFromParcel(Parcel in) {
return new CustomParcelable(in);
}
+
@Override
public CustomParcelable[] newArray(int size) {
return new CustomParcelable[size];
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index 26f6d69..442394e3 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -347,4 +347,30 @@
p.recycle();
Binder.setIsDirectlyHandlingTransactionOverride(false);
}
+
+ @Test
+ @IgnoreUnderRavenwood(blockedBy = Parcel.class)
+ public void testHasBinders_AfterWritingBinderToParcel() {
+ Binder binder = new Binder();
+ Parcel pA = Parcel.obtain();
+ int iA = pA.dataPosition();
+ pA.writeInt(13);
+ assertFalse(pA.hasBinders());
+ pA.writeStrongBinder(binder);
+ assertTrue(pA.hasBinders());
+ }
+
+
+ @Test
+ @IgnoreUnderRavenwood(blockedBy = Parcel.class)
+ public void testHasBindersInRange_AfterWritingBinderToParcel() {
+ Binder binder = new Binder();
+ Parcel pA = Parcel.obtain();
+ pA.writeInt(13);
+
+ int binderStartPos = pA.dataPosition();
+ pA.writeStrongBinder(binder);
+ int binderEndPos = pA.dataPosition();
+ assertTrue(pA.hasBinders(binderStartPos, binderEndPos - binderStartPos));
+ }
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 652011b..41b67ce 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -81,6 +81,7 @@
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -462,6 +463,7 @@
*/
@UiThreadTest
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_getDefaultValues() {
ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
@@ -478,6 +480,7 @@
* Also, mIsFrameRateBoosting should be true when the visibility becomes visible
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateCategory_visibility_bySize() {
@@ -511,6 +514,7 @@
* <7%: FRAME_RATE_CATEGORY_LOW
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateCategory_smallSize_bySize() {
@@ -539,6 +543,7 @@
* >=7% : FRAME_RATE_CATEGORY_NORMAL
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateCategory_normalSize_bySize() {
@@ -571,6 +576,7 @@
* Also, mIsFrameRateBoosting should be true when the visibility becomes visible
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_voteFrameRateCategory_visibility_defaultHigh() {
View view = new View(sContext);
@@ -603,6 +609,7 @@
* <7%: FRAME_RATE_CATEGORY_NORMAL
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_voteFrameRateCategory_smallSize_defaultHigh() {
View view = new View(sContext);
@@ -630,6 +637,7 @@
* >=7% : FRAME_RATE_CATEGORY_HIGH
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_voteFrameRateCategory_normalSize_defaultHigh() {
View view = new View(sContext);
@@ -659,6 +667,7 @@
* It should take the max value among all of the voted categories per frame.
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_voteFrameRateCategory_aggregate() {
View view = new View(sContext);
@@ -704,6 +713,7 @@
* prioritize 60Hz..
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_voteFrameRate_aggregate() {
View view = new View(sContext);
@@ -762,6 +772,7 @@
* submit your preferred choice to the ViewRootImpl.
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_voteFrameRate_category() {
View view = new View(sContext);
@@ -801,6 +812,7 @@
* Also, we shouldn't call setFrameRate.
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, FLAG_VIEW_VELOCITY_API})
public void votePreferredFrameRate_voteFrameRateCategory_velocityToHigh() {
View view = new View(sContext);
@@ -832,6 +844,7 @@
* We should boost the frame rate if the value of mInsetsAnimationRunning is true.
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_insetsAnimation() {
View view = new View(sContext);
@@ -868,6 +881,7 @@
* Test FrameRateBoostOnTouchEnabled API
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_frameRateBoostOnTouch() {
View view = new View(sContext);
@@ -900,6 +914,7 @@
* mPreferredFrameRate should be set to 0.
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_voteFrameRateTimeOut() throws InterruptedException {
final long delay = 200L;
@@ -937,6 +952,7 @@
* A View should either vote a frame rate or a frame rate category instead of both.
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_voteFrameRateOnly() {
View view = new View(sContext);
@@ -979,6 +995,7 @@
* - otherwise, use the previous category value.
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_infrequentLayer_defaultHigh() throws InterruptedException {
final long delay = 200L;
@@ -1039,6 +1056,7 @@
*/
@UiThreadTest
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_isFrameRatePowerSavingsBalanced() {
ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
@@ -1056,6 +1074,7 @@
* 2. If FT2-FT1 > 15ms && FT3-FT2 > 15ms -> vote for NORMAL category
*/
@Test
+ @Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_applyTextureViewHeuristic() throws InterruptedException {
final long delay = 30L;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 60a436e..745390d 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -25,7 +25,6 @@
import static androidx.test.espresso.matcher.RootMatchers.isDialog;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static com.google.common.truth.Truth.assertThat;
@@ -54,7 +53,6 @@
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Handler;
-import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -176,21 +174,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
- public void selectTestService_oldPermissionDialog_deny_dialogIsHidden() {
- launchActivity();
- openShortcutsList();
-
- mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS);
- onView(withText(DENY_LABEL)).perform(scrollTo(), click());
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-
- onView(withId(R.id.accessibility_permissionDialog_title)).inRoot(isDialog()).check(
- doesNotExist());
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void selectTestService_permissionDialog_allow_rowChecked() {
launchActivity();
openShortcutsList();
@@ -202,7 +185,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void selectTestService_permissionDialog_deny_rowNotChecked() {
launchActivity();
openShortcutsList();
@@ -214,7 +196,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void selectTestService_permissionDialog_uninstall_callsUninstaller_rowRemoved() {
launchActivity();
openShortcutsList();
@@ -228,7 +209,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void selectTestService_permissionDialog_notShownWhenNotRequired() throws Exception {
when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any()))
.thenReturn(false);
@@ -243,7 +223,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void selectTestService_notPermittedByAdmin_blockedEvenIfNoWarningRequired()
throws Exception {
when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any()))
@@ -380,11 +359,9 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (Flags.cleanupAccessibilityWarningDialog()) {
- // Setting the Theme is necessary here for the dialog to use the proper style
- // resources as designated in its layout XML.
- setTheme(R.style.Theme_DeviceDefault_DayNight);
- }
+ // Setting the Theme is necessary here for the dialog to use the proper style
+ // resources as designated in its layout XML.
+ setTheme(R.style.Theme_DeviceDefault_DayNight);
}
@Override
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
index 24aab61..362eeea 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
@@ -25,7 +25,6 @@
import android.app.AlertDialog;
import android.content.Context;
import android.os.RemoteException;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
@@ -57,8 +56,6 @@
*/
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-@RequiresFlagsEnabled(
- android.view.accessibility.Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public class AccessibilityServiceWarningTest {
private static final String A11Y_SERVICE_PACKAGE_LABEL = "TestA11yService";
private static final String A11Y_SERVICE_SUMMARY = "TestA11yService summary";
diff --git a/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java b/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java
new file mode 100644
index 0000000..e361ce3
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.net;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConnectivityBlobStoreTest {
+ private static final String DATABASE_FILENAME = "ConnectivityBlobStore.db";
+
+ private Context mContext;
+ private File mFile;
+
+ private ConnectivityBlobStore createConnectivityBlobStore() {
+ return new ConnectivityBlobStore(mFile);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mFile = mContext.getDatabasePath(DATABASE_FILENAME);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mContext.deleteDatabase(DATABASE_FILENAME);
+ }
+
+ @Test
+ public void testFileCreateDelete() {
+ assertFalse(mFile.exists());
+ createConnectivityBlobStore();
+ assertTrue(mFile.exists());
+
+ assertTrue(mContext.deleteDatabase(DATABASE_FILENAME));
+ assertFalse(mFile.exists());
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/net/OWNERS b/core/tests/coretests/src/com/android/internal/net/OWNERS
new file mode 100644
index 0000000..f51ba47
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/net/OWNERS
@@ -0,0 +1 @@
+include /core/java/com/android/internal/net/OWNERS
diff --git a/libs/WindowManager/Shell/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp
index 1686d0d..1ad19c9 100644
--- a/libs/WindowManager/Shell/multivalentTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentTests/Android.bp
@@ -46,6 +46,7 @@
exclude_srcs: ["src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt"],
static_libs: [
"junit",
+ "androidx.core_core-animation-testing",
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
@@ -64,6 +65,7 @@
static_libs: [
"WindowManager-Shell",
"junit",
+ "androidx.core_core-animation-testing",
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetControllerTest.kt
new file mode 100644
index 0000000..2ac7791
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetControllerTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar
+
+import android.content.Context
+import android.graphics.Insets
+import android.graphics.Rect
+import android.view.View
+import android.view.WindowManager
+import android.widget.FrameLayout
+import androidx.core.animation.AnimatorTestRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.BubblePositioner
+import com.android.wm.shell.bubbles.DeviceConfig
+import com.android.wm.shell.bubbles.bar.BubbleBarDropTargetController.Companion.DROP_TARGET_ALPHA_IN_DURATION
+import com.android.wm.shell.bubbles.bar.BubbleBarDropTargetController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
+import com.android.wm.shell.bubbles.bar.BubbleBarDropTargetController.Companion.DROP_TARGET_SCALE
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests for [BubbleBarDropTargetController] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleBarDropTargetControllerTest {
+
+ companion object {
+ @JvmField @ClassRule val animatorTestRule: AnimatorTestRule = AnimatorTestRule()
+ }
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private lateinit var controller: BubbleBarDropTargetController
+ private lateinit var positioner: BubblePositioner
+ private lateinit var container: FrameLayout
+
+ @Before
+ fun setUp() {
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ container = FrameLayout(context)
+ val windowManager = context.getSystemService(WindowManager::class.java)
+ positioner = BubblePositioner(context, windowManager)
+ positioner.setShowingInBubbleBar(true)
+ val deviceConfig =
+ DeviceConfig(
+ windowBounds = Rect(0, 0, 2000, 2600),
+ isLargeScreen = true,
+ isSmallTablet = false,
+ isLandscape = true,
+ isRtl = false,
+ insets = Insets.of(10, 20, 30, 40)
+ )
+ positioner.update(deviceConfig)
+ positioner.bubbleBarBounds = Rect(1800, 2400, 1970, 2560)
+
+ controller = BubbleBarDropTargetController(context, container, positioner)
+ }
+
+ @Test
+ fun show_moveLeftToRight_isVisibleWithExpectedBounds() {
+ val expectedBoundsOnLeft = getExpectedDropTargetBounds(onLeft = true)
+ val expectedBoundsOnRight = getExpectedDropTargetBounds(onLeft = false)
+
+ runOnMainSync { controller.show(BubbleBarLocation.LEFT) }
+ waitForAnimateIn()
+ val viewOnLeft = getDropTargetView()
+ assertThat(viewOnLeft).isNotNull()
+ assertThat(viewOnLeft!!.alpha).isEqualTo(1f)
+ assertThat(viewOnLeft.layoutParams.width).isEqualTo(expectedBoundsOnLeft.width())
+ assertThat(viewOnLeft.layoutParams.height).isEqualTo(expectedBoundsOnLeft.height())
+ assertThat(viewOnLeft.x).isEqualTo(expectedBoundsOnLeft.left)
+ assertThat(viewOnLeft.y).isEqualTo(expectedBoundsOnLeft.top)
+
+ runOnMainSync { controller.show(BubbleBarLocation.RIGHT) }
+ waitForAnimateOut()
+ waitForAnimateIn()
+ val viewOnRight = getDropTargetView()
+ assertThat(viewOnRight).isNotNull()
+ assertThat(viewOnRight!!.alpha).isEqualTo(1f)
+ assertThat(viewOnRight.layoutParams.width).isEqualTo(expectedBoundsOnRight.width())
+ assertThat(viewOnRight.layoutParams.height).isEqualTo(expectedBoundsOnRight.height())
+ assertThat(viewOnRight.x).isEqualTo(expectedBoundsOnRight.left)
+ assertThat(viewOnRight.y).isEqualTo(expectedBoundsOnRight.top)
+ }
+
+ @Test
+ fun toggleSetHidden_dropTargetShown_updatesAlpha() {
+ runOnMainSync { controller.show(BubbleBarLocation.RIGHT) }
+ waitForAnimateIn()
+ val view = getDropTargetView()
+ assertThat(view).isNotNull()
+ assertThat(view!!.alpha).isEqualTo(1f)
+
+ runOnMainSync { controller.setHidden(true) }
+ waitForAnimateOut()
+ val hiddenView = getDropTargetView()
+ assertThat(hiddenView).isNotNull()
+ assertThat(hiddenView!!.alpha).isEqualTo(0f)
+
+ runOnMainSync { controller.setHidden(false) }
+ waitForAnimateIn()
+ val shownView = getDropTargetView()
+ assertThat(shownView).isNotNull()
+ assertThat(shownView!!.alpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun toggleSetHidden_dropTargetNotShown_viewNotCreated() {
+ runOnMainSync { controller.setHidden(true) }
+ waitForAnimateOut()
+ assertThat(getDropTargetView()).isNull()
+ runOnMainSync { controller.setHidden(false) }
+ waitForAnimateIn()
+ assertThat(getDropTargetView()).isNull()
+ }
+
+ @Test
+ fun dismiss_dropTargetShown_viewRemoved() {
+ runOnMainSync { controller.show(BubbleBarLocation.LEFT) }
+ waitForAnimateIn()
+ assertThat(getDropTargetView()).isNotNull()
+ runOnMainSync { controller.dismiss() }
+ waitForAnimateOut()
+ assertThat(getDropTargetView()).isNull()
+ }
+
+ @Test
+ fun dismiss_dropTargetNotShown_doesNothing() {
+ runOnMainSync { controller.dismiss() }
+ waitForAnimateOut()
+ assertThat(getDropTargetView()).isNull()
+ }
+
+ private fun getDropTargetView(): View? = container.findViewById(R.id.bubble_bar_drop_target)
+
+ private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect {
+ val rect = Rect()
+ positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, rect)
+ // Scale the rect to expected size, but keep the center point the same
+ val centerX = rect.centerX()
+ val centerY = rect.centerY()
+ rect.scale(DROP_TARGET_SCALE)
+ rect.offset(centerX - rect.centerX(), centerY - rect.centerY())
+ return rect
+ }
+
+ private fun runOnMainSync(runnable: Runnable) {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable)
+ }
+
+ private fun waitForAnimateIn() {
+ // Advance animator for on-device test
+ runOnMainSync { animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_IN_DURATION) }
+ }
+
+ private fun waitForAnimateOut() {
+ // Advance animator for on-device test
+ runOnMainSync { animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_OUT_DURATION) }
+ }
+}
diff --git a/libs/WindowManager/Shell/res/color/bubble_drop_target_background_color.xml b/libs/WindowManager/Shell/res/color/bubble_drop_target_background_color.xml
new file mode 100644
index 0000000..ab1ab98
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/bubble_drop_target_background_color.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:alpha="0.35" android:color="?androidprv:attr/materialColorPrimaryContainer" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml b/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml
index 468b5c2..9dcde3b 100644
--- a/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,9 +13,12 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape android:shape="rectangle"
- xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="#bf309fb5" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
<corners android:radius="@dimen/bubble_bar_expanded_view_corner_radius" />
- <stroke android:width="1dp" android:color="#A00080FF"/>
+ <solid android:color="@color/bubble_drop_target_background_color" />
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/materialColorPrimaryContainer" />
</shape>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetController.kt
index 55ec6cd..f6b4653 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetController.kt
@@ -21,6 +21,10 @@
import android.view.View
import android.widget.FrameLayout
import android.widget.FrameLayout.LayoutParams
+import androidx.annotation.VisibleForTesting
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorListenerAdapter
+import androidx.core.animation.ObjectAnimator
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner
import com.android.wm.shell.common.bubbles.BubbleBarLocation
@@ -33,6 +37,7 @@
) {
private var dropTargetView: View? = null
+ private var animator: ObjectAnimator? = null
private val tempRect: Rect by lazy(LazyThreadSafetyMode.NONE) { Rect() }
/**
@@ -57,7 +62,8 @@
/**
* Set the view hidden or not
*
- * Requires the drop target to be first shown by calling [show]. Otherwise does not do anything.
+ * Requires the drop target to be first shown by calling [animateIn]. Otherwise does not do
+ * anything.
*/
fun setHidden(hidden: Boolean) {
val targetView = dropTargetView ?: return
@@ -106,20 +112,40 @@
}
private fun View.animateIn() {
- animate().alpha(1f).setDuration(DROP_TARGET_ALPHA_IN_DURATION).start()
+ animator?.cancel()
+ animator =
+ ObjectAnimator.ofFloat(this, View.ALPHA, 1f)
+ .setDuration(DROP_TARGET_ALPHA_IN_DURATION)
+ .addEndAction { animator = null }
+ animator?.start()
}
private fun View.animateOut(endAction: Runnable? = null) {
- animate()
- .alpha(0f)
- .setDuration(DROP_TARGET_ALPHA_OUT_DURATION)
- .withEndAction(endAction)
- .start()
+ animator?.cancel()
+ animator =
+ ObjectAnimator.ofFloat(this, View.ALPHA, 0f)
+ .setDuration(DROP_TARGET_ALPHA_OUT_DURATION)
+ .addEndAction {
+ endAction?.run()
+ animator = null
+ }
+ animator?.start()
+ }
+
+ private fun <T : Animator> T.addEndAction(runnable: Runnable): T {
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ runnable.run()
+ }
+ }
+ )
+ return this
}
companion object {
- private const val DROP_TARGET_ALPHA_IN_DURATION = 150L
- private const val DROP_TARGET_ALPHA_OUT_DURATION = 100L
- private const val DROP_TARGET_SCALE = 0.9f
+ @VisibleForTesting const val DROP_TARGET_ALPHA_IN_DURATION = 150L
+ @VisibleForTesting const val DROP_TARGET_ALPHA_OUT_DURATION = 100L
+ @VisibleForTesting const val DROP_TARGET_SCALE = 0.9f
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 838603f..5889da1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -49,7 +49,7 @@
/** Called when requested to go to desktop mode from the current focused app. */
- void enterDesktop(int displayId);
+ void moveFocusedTaskToDesktop(int displayId);
/** Called when requested to go to fullscreen from the current focused desktop app. */
void moveFocusedTaskToFullscreen(int displayId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 992e5ae..cdef4fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -263,7 +263,7 @@
}
/** Enter desktop by using the focused task in given `displayId` */
- fun enterDesktop(displayId: Int) {
+ fun moveFocusedTaskToDesktop(displayId: Int) {
val allFocusedTasks =
shellTaskOrganizer.getRunningTasks(displayId).filter { taskInfo ->
taskInfo.isFocused &&
@@ -1212,9 +1212,9 @@
}
}
- override fun enterDesktop(displayId: Int) {
+ override fun moveFocusedTaskToDesktop(displayId: Int) {
mainExecutor.execute {
- this@DesktopTasksController.enterDesktop(displayId)
+ this@DesktopTasksController.moveFocusedTaskToDesktop(displayId)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 254bf7d..4fbf2bd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -833,7 +833,7 @@
verify(launchAdjacentController).launchAdjacentEnabled = true
}
@Test
- fun enterDesktop_fullscreenTaskIsMovedToDesktop() {
+ fun moveFocusedTaskToDesktop_fullscreenTaskIsMovedToDesktop() {
val task1 = setUpFullscreenTask()
val task2 = setUpFullscreenTask()
val task3 = setUpFullscreenTask()
@@ -842,7 +842,7 @@
task2.isFocused = false
task3.isFocused = false
- controller.enterDesktop(DEFAULT_DISPLAY)
+ controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task1.token.asBinder()]?.windowingMode)
@@ -850,7 +850,7 @@
}
@Test
- fun enterDesktop_splitScreenTaskIsMovedToDesktop() {
+ fun moveFocusedTaskToDesktop_splitScreenTaskIsMovedToDesktop() {
val task1 = setUpSplitScreenTask()
val task2 = setUpFullscreenTask()
val task3 = setUpFullscreenTask()
@@ -863,7 +863,7 @@
task4.parentTaskId = task1.taskId
- controller.enterDesktop(DEFAULT_DISPLAY)
+ controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task4.token.asBinder()]?.windowingMode)
diff --git a/nfc/Android.bp b/nfc/Android.bp
index 7698e2b..ca10949 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -50,7 +50,7 @@
],
defaults: ["framework-module-defaults"],
sdk_version: "module_current",
- min_sdk_version: "34", // should be 35 (making it 34 for compiling for `-next`)
+ min_sdk_version: "VanillaIceCream",
installable: true,
optimize: {
enabled: false,
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index 37b5d40..a8d8f9a 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -26,6 +26,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
+import android.crashrecovery.flags.Flags;
import android.net.ConnectivityModuleConnector;
import android.os.Environment;
import android.os.Handler;
@@ -57,16 +58,20 @@
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -130,8 +135,25 @@
@VisibleForTesting
static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5;
- @VisibleForTesting
+
static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10);
+ // Boot loop at which packageWatchdog starts first mitigation
+ private static final String BOOT_LOOP_THRESHOLD =
+ "persist.device_config.configuration.boot_loop_threshold";
+ @VisibleForTesting
+ static final int DEFAULT_BOOT_LOOP_THRESHOLD = 15;
+ // Once boot_loop_threshold is surpassed next mitigation would be triggered after
+ // specified number of reboots.
+ private static final String BOOT_LOOP_MITIGATION_INCREMENT =
+ "persist.device_config.configuration..boot_loop_mitigation_increment";
+ @VisibleForTesting
+ static final int DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT = 2;
+
+ // Threshold level at which or above user might experience significant disruption.
+ private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
+ "persist.device_config.configuration.major_user_impact_level_threshold";
+ private static final int DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
private long mNumberOfNativeCrashPollsRemaining;
@@ -145,6 +167,7 @@
private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration";
private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
private static final String ATTR_MITIGATION_CALLS = "mitigation-calls";
+ private static final String ATTR_MITIGATION_COUNT = "mitigation-count";
// A file containing information about the current mitigation count in the case of a boot loop.
// This allows boot loop information to persist in the case of an fs-checkpoint being
@@ -230,8 +253,16 @@
mConnectivityModuleConnector = connectivityModuleConnector;
mSystemClock = clock;
mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
- mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
- DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS);
+ if (Flags.recoverabilityDetection()) {
+ mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS,
+ SystemProperties.getInt(BOOT_LOOP_MITIGATION_INCREMENT,
+ DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT));
+ } else {
+ mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS);
+ }
+
loadFromFile();
sPackageWatchdog = this;
}
@@ -436,8 +467,13 @@
mitigationCount =
currentMonitoredPackage.getMitigationCountLocked();
}
- currentObserverToNotify.execute(versionedPackage,
- failureReason, mitigationCount);
+ if (Flags.recoverabilityDetection()) {
+ maybeExecute(currentObserverToNotify, versionedPackage,
+ failureReason, currentObserverImpact, mitigationCount);
+ } else {
+ currentObserverToNotify.execute(versionedPackage,
+ failureReason, mitigationCount);
+ }
}
}
}
@@ -467,37 +503,76 @@
}
}
if (currentObserverToNotify != null) {
- currentObserverToNotify.execute(failingPackage, failureReason, 1);
+ if (Flags.recoverabilityDetection()) {
+ maybeExecute(currentObserverToNotify, failingPackage, failureReason,
+ currentObserverImpact, /*mitigationCount=*/ 1);
+ } else {
+ currentObserverToNotify.execute(failingPackage, failureReason, 1);
+ }
}
}
+ private void maybeExecute(PackageHealthObserver currentObserverToNotify,
+ VersionedPackage versionedPackage,
+ @FailureReasons int failureReason,
+ int currentObserverImpact,
+ int mitigationCount) {
+ if (currentObserverImpact < getUserImpactLevelLimit()) {
+ currentObserverToNotify.execute(versionedPackage, failureReason, mitigationCount);
+ }
+ }
+
+
/**
* Called when the system server boots. If the system server is detected to be in a boot loop,
* query each observer and perform the mitigation action with the lowest user impact.
*/
+ @SuppressWarnings("GuardedBy")
public void noteBoot() {
synchronized (mLock) {
- if (mBootThreshold.incrementAndTest()) {
- mBootThreshold.reset();
+ boolean mitigate = mBootThreshold.incrementAndTest();
+ if (mitigate) {
+ if (!Flags.recoverabilityDetection()) {
+ mBootThreshold.reset();
+ }
int mitigationCount = mBootThreshold.getMitigationCount() + 1;
PackageHealthObserver currentObserverToNotify = null;
+ ObserverInternal currentObserverInternal = null;
int currentObserverImpact = Integer.MAX_VALUE;
for (int i = 0; i < mAllObservers.size(); i++) {
final ObserverInternal observer = mAllObservers.valueAt(i);
PackageHealthObserver registeredObserver = observer.registeredObserver;
if (registeredObserver != null) {
- int impact = registeredObserver.onBootLoop(mitigationCount);
+ int impact = Flags.recoverabilityDetection()
+ ? registeredObserver.onBootLoop(
+ observer.getBootMitigationCount() + 1)
+ : registeredObserver.onBootLoop(mitigationCount);
if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
currentObserverToNotify = registeredObserver;
+ currentObserverInternal = observer;
currentObserverImpact = impact;
}
}
}
if (currentObserverToNotify != null) {
- mBootThreshold.setMitigationCount(mitigationCount);
- mBootThreshold.saveMitigationCountToMetadata();
- currentObserverToNotify.executeBootLoopMitigation(mitigationCount);
+ if (Flags.recoverabilityDetection()) {
+ if (currentObserverImpact < getUserImpactLevelLimit()
+ || (currentObserverImpact >= getUserImpactLevelLimit()
+ && mBootThreshold.getCount() >= getBootLoopThreshold())) {
+ int currentObserverMitigationCount =
+ currentObserverInternal.getBootMitigationCount() + 1;
+ currentObserverInternal.setBootMitigationCount(
+ currentObserverMitigationCount);
+ saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
+ currentObserverToNotify.executeBootLoopMitigation(
+ currentObserverMitigationCount);
+ }
+ } else {
+ mBootThreshold.setMitigationCount(mitigationCount);
+ mBootThreshold.saveMitigationCountToMetadata();
+ currentObserverToNotify.executeBootLoopMitigation(mitigationCount);
+ }
}
}
}
@@ -567,13 +642,27 @@
mShortTaskHandler.post(()->checkAndMitigateNativeCrashes());
}
+ private int getUserImpactLevelLimit() {
+ return SystemProperties.getInt(MAJOR_USER_IMPACT_LEVEL_THRESHOLD,
+ DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD);
+ }
+
+ private int getBootLoopThreshold() {
+ return SystemProperties.getInt(BOOT_LOOP_THRESHOLD,
+ DEFAULT_BOOT_LOOP_THRESHOLD);
+ }
+
/** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
@Retention(SOURCE)
@IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_20,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_71,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_75,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_80,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_90,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
public @interface PackageHealthObserverImpact {
@@ -582,11 +671,15 @@
/* Action has low user impact, user of a device will barely notice. */
int USER_IMPACT_LEVEL_10 = 10;
/* Actions having medium user impact, user of a device will likely notice. */
+ int USER_IMPACT_LEVEL_20 = 20;
int USER_IMPACT_LEVEL_30 = 30;
int USER_IMPACT_LEVEL_50 = 50;
int USER_IMPACT_LEVEL_70 = 70;
- int USER_IMPACT_LEVEL_90 = 90;
/* Action has high user impact, a last resort, user of a device will be very frustrated. */
+ int USER_IMPACT_LEVEL_71 = 71;
+ int USER_IMPACT_LEVEL_75 = 75;
+ int USER_IMPACT_LEVEL_80 = 80;
+ int USER_IMPACT_LEVEL_90 = 90;
int USER_IMPACT_LEVEL_100 = 100;
}
@@ -1144,6 +1237,12 @@
}
}
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void registerObserverInternal(ObserverInternal observerInternal) {
+ mAllObservers.put(observerInternal.name, observerInternal);
+ }
+
/**
* Represents an observer monitoring a set of packages along with the failure thresholds for
* each package.
@@ -1151,17 +1250,23 @@
* <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
* instances of this class.
*/
- private static class ObserverInternal {
+ static class ObserverInternal {
public final String name;
@GuardedBy("mLock")
private final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>();
@Nullable
@GuardedBy("mLock")
public PackageHealthObserver registeredObserver;
+ private int mMitigationCount;
ObserverInternal(String name, List<MonitoredPackage> packages) {
+ this(name, packages, /*mitigationCount=*/ 0);
+ }
+
+ ObserverInternal(String name, List<MonitoredPackage> packages, int mitigationCount) {
this.name = name;
updatePackagesLocked(packages);
+ this.mMitigationCount = mitigationCount;
}
/**
@@ -1173,6 +1278,9 @@
try {
out.startTag(null, TAG_OBSERVER);
out.attribute(null, ATTR_NAME, name);
+ if (Flags.recoverabilityDetection()) {
+ out.attributeInt(null, ATTR_MITIGATION_COUNT, mMitigationCount);
+ }
for (int i = 0; i < mPackages.size(); i++) {
MonitoredPackage p = mPackages.valueAt(i);
p.writeLocked(out);
@@ -1185,6 +1293,14 @@
}
}
+ public int getBootMitigationCount() {
+ return mMitigationCount;
+ }
+
+ public void setBootMitigationCount(int mitigationCount) {
+ mMitigationCount = mitigationCount;
+ }
+
@GuardedBy("mLock")
public void updatePackagesLocked(List<MonitoredPackage> packages) {
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
@@ -1289,6 +1405,7 @@
**/
public static ObserverInternal read(TypedXmlPullParser parser, PackageWatchdog watchdog) {
String observerName = null;
+ int observerMitigationCount = 0;
if (TAG_OBSERVER.equals(parser.getName())) {
observerName = parser.getAttributeValue(null, ATTR_NAME);
if (TextUtils.isEmpty(observerName)) {
@@ -1299,6 +1416,9 @@
List<MonitoredPackage> packages = new ArrayList<>();
int innerDepth = parser.getDepth();
try {
+ if (Flags.recoverabilityDetection()) {
+ observerMitigationCount = parser.getAttributeInt(null, ATTR_MITIGATION_COUNT);
+ }
while (XmlUtils.nextElementWithin(parser, innerDepth)) {
if (TAG_PACKAGE.equals(parser.getName())) {
try {
@@ -1319,7 +1439,7 @@
if (packages.isEmpty()) {
return null;
}
- return new ObserverInternal(observerName, packages);
+ return new ObserverInternal(observerName, packages, observerMitigationCount);
}
/** Dumps information about this observer and the packages it watches. */
@@ -1679,6 +1799,27 @@
}
}
+ @GuardedBy("mLock")
+ @SuppressWarnings("GuardedBy")
+ void saveAllObserversBootMitigationCountToMetadata(String filePath) {
+ HashMap<String, Integer> bootMitigationCounts = new HashMap<>();
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ final ObserverInternal observer = mAllObservers.valueAt(i);
+ bootMitigationCounts.put(observer.name, observer.getBootMitigationCount());
+ }
+
+ try {
+ FileOutputStream fileStream = new FileOutputStream(new File(filePath));
+ ObjectOutputStream objectStream = new ObjectOutputStream(fileStream);
+ objectStream.writeObject(bootMitigationCounts);
+ objectStream.flush();
+ objectStream.close();
+ fileStream.close();
+ } catch (Exception e) {
+ Slog.i(TAG, "Could not save observers metadata to file: " + e);
+ }
+ }
+
/**
* Handles the thresholding logic for system server boots.
*/
@@ -1686,10 +1827,16 @@
private final int mBootTriggerCount;
private final long mTriggerWindow;
+ private final int mBootMitigationIncrement;
BootThreshold(int bootTriggerCount, long triggerWindow) {
+ this(bootTriggerCount, triggerWindow, /*bootMitigationIncrement=*/ 1);
+ }
+
+ BootThreshold(int bootTriggerCount, long triggerWindow, int bootMitigationIncrement) {
this.mBootTriggerCount = bootTriggerCount;
this.mTriggerWindow = triggerWindow;
+ this.mBootMitigationIncrement = bootMitigationIncrement;
}
public void reset() {
@@ -1761,8 +1908,13 @@
/** Increments the boot counter, and returns whether the device is bootlooping. */
+ @GuardedBy("mLock")
public boolean incrementAndTest() {
- readMitigationCountFromMetadataIfNecessary();
+ if (Flags.recoverabilityDetection()) {
+ readAllObserversBootMitigationCountIfNecessary(METADATA_FILE);
+ } else {
+ readMitigationCountFromMetadataIfNecessary();
+ }
final long now = mSystemClock.uptimeMillis();
if (now - getStart() < 0) {
Slog.e(TAG, "Window was less than zero. Resetting start to current time.");
@@ -1770,8 +1922,12 @@
setMitigationStart(now);
}
if (now - getMitigationStart() > DEFAULT_DEESCALATION_WINDOW_MS) {
- setMitigationCount(0);
setMitigationStart(now);
+ if (Flags.recoverabilityDetection()) {
+ resetAllObserversBootMitigationCount();
+ } else {
+ setMitigationCount(0);
+ }
}
final long window = now - getStart();
if (window >= mTriggerWindow) {
@@ -1782,9 +1938,48 @@
int count = getCount() + 1;
setCount(count);
EventLogTags.writeRescueNote(Process.ROOT_UID, count, window);
+ if (Flags.recoverabilityDetection()) {
+ boolean mitigate = (count >= mBootTriggerCount)
+ && (count - mBootTriggerCount) % mBootMitigationIncrement == 0;
+ return mitigate;
+ }
return count >= mBootTriggerCount;
}
}
+ @GuardedBy("mLock")
+ private void resetAllObserversBootMitigationCount() {
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ final ObserverInternal observer = mAllObservers.valueAt(i);
+ observer.setBootMitigationCount(0);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @SuppressWarnings("GuardedBy")
+ void readAllObserversBootMitigationCountIfNecessary(String filePath) {
+ File metadataFile = new File(filePath);
+ if (metadataFile.exists()) {
+ try {
+ FileInputStream fileStream = new FileInputStream(metadataFile);
+ ObjectInputStream objectStream = new ObjectInputStream(fileStream);
+ HashMap<String, Integer> bootMitigationCounts =
+ (HashMap<String, Integer>) objectStream.readObject();
+ objectStream.close();
+ fileStream.close();
+
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ final ObserverInternal observer = mAllObservers.valueAt(i);
+ if (bootMitigationCounts.containsKey(observer.name)) {
+ observer.setBootMitigationCount(
+ bootMitigationCounts.get(observer.name));
+ }
+ }
+ } catch (Exception e) {
+ Slog.i(TAG, "Could not read observer metadata file: " + e);
+ }
+ }
+ }
+
}
}
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 7bdc1a0..7093ba4 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -20,6 +20,7 @@
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
@@ -27,6 +28,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
+import android.crashrecovery.flags.Flags;
import android.os.Build;
import android.os.Environment;
import android.os.PowerManager;
@@ -53,6 +55,8 @@
import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -89,6 +93,40 @@
@VisibleForTesting
static final int LEVEL_FACTORY_RESET = 5;
@VisibleForTesting
+ static final int RESCUE_LEVEL_NONE = 0;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET = 1;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET = 2;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_WARM_REBOOT = 3;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 4;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 5;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 6;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_FACTORY_RESET = 7;
+
+ @IntDef(prefix = { "RESCUE_LEVEL_" }, value = {
+ RESCUE_LEVEL_NONE,
+ RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET,
+ RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET,
+ RESCUE_LEVEL_WARM_REBOOT,
+ RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
+ RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
+ RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
+ RESCUE_LEVEL_FACTORY_RESET
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface RescueLevels {}
+
+ @VisibleForTesting
+ static final String RESCUE_NON_REBOOT_LEVEL_LIMIT = "persist.sys.rescue_non_reboot_level_limit";
+ @VisibleForTesting
+ static final int DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT = RESCUE_LEVEL_WARM_REBOOT - 1;
+ @VisibleForTesting
static final String TAG = "RescueParty";
@VisibleForTesting
static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
@@ -347,11 +385,20 @@
}
private static int getMaxRescueLevel(boolean mayPerformReboot) {
- if (!mayPerformReboot
- || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
- return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
+ if (Flags.recoverabilityDetection()) {
+ if (!mayPerformReboot
+ || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
+ return SystemProperties.getInt(RESCUE_NON_REBOOT_LEVEL_LIMIT,
+ DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT);
+ }
+ return RESCUE_LEVEL_FACTORY_RESET;
+ } else {
+ if (!mayPerformReboot
+ || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
+ return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
+ }
+ return LEVEL_FACTORY_RESET;
}
- return LEVEL_FACTORY_RESET;
}
/**
@@ -379,6 +426,46 @@
}
}
+ /**
+ * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
+ * When failedPackage is null then 1st and 2nd mitigation counts are redundant (scoped and
+ * all device config reset). Behaves as if one mitigation attempt was already done.
+ *
+ * @param mitigationCount the mitigation attempt number (1 = first attempt etc.).
+ * @param mayPerformReboot whether or not a reboot and factory reset may be performed
+ * for the given failure.
+ * @param failedPackage in case of bootloop this is null.
+ * @return the rescue level for the n-th mitigation attempt.
+ */
+ private static @RescueLevels int getRescueLevel(int mitigationCount, boolean mayPerformReboot,
+ @Nullable VersionedPackage failedPackage) {
+ // Skipping RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET since it's not defined without a failed
+ // package.
+ if (failedPackage == null && mitigationCount > 0) {
+ mitigationCount += 1;
+ }
+ if (mitigationCount == 1) {
+ return RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET;
+ } else if (mitigationCount == 2) {
+ return RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET;
+ } else if (mitigationCount == 3) {
+ return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_WARM_REBOOT);
+ } else if (mitigationCount == 4) {
+ return Math.min(getMaxRescueLevel(mayPerformReboot),
+ RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS);
+ } else if (mitigationCount == 5) {
+ return Math.min(getMaxRescueLevel(mayPerformReboot),
+ RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES);
+ } else if (mitigationCount == 6) {
+ return Math.min(getMaxRescueLevel(mayPerformReboot),
+ RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS);
+ } else if (mitigationCount >= 7) {
+ return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_FACTORY_RESET);
+ } else {
+ return RESCUE_LEVEL_NONE;
+ }
+ }
+
private static void executeRescueLevel(Context context, @Nullable String failedPackage,
int level) {
Slog.w(TAG, "Attempting rescue level " + levelToString(level));
@@ -397,6 +484,15 @@
private static void executeRescueLevelInternal(Context context, int level, @Nullable
String failedPackage) throws Exception {
+ if (Flags.recoverabilityDetection()) {
+ executeRescueLevelInternalNew(context, level, failedPackage);
+ } else {
+ executeRescueLevelInternalOld(context, level, failedPackage);
+ }
+ }
+
+ private static void executeRescueLevelInternalOld(Context context, int level, @Nullable
+ String failedPackage) throws Exception {
if (level <= LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS) {
// Disabling flag resets on master branch for trunk stable launch.
@@ -410,8 +506,6 @@
// Try our best to reset all settings possible, and once finished
// rethrow any exception that we encountered
Exception res = null;
- Runnable runnable;
- Thread thread;
switch (level) {
case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
try {
@@ -453,21 +547,7 @@
}
break;
case LEVEL_WARM_REBOOT:
- // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
- // when device shutting down.
- setRebootProperty(true);
- runnable = () -> {
- try {
- PowerManager pm = context.getSystemService(PowerManager.class);
- if (pm != null) {
- pm.reboot(TAG);
- }
- } catch (Throwable t) {
- logRescueException(level, failedPackage, t);
- }
- };
- thread = new Thread(runnable);
- thread.start();
+ executeWarmReboot(context, level, failedPackage);
break;
case LEVEL_FACTORY_RESET:
// Before the completion of Reboot, if any crash happens then PackageWatchdog
@@ -475,23 +555,9 @@
// Adding a check to prevent factory reset to execute before above reboot completes.
// Note: this reboot property is not persistent resets after reboot is completed.
if (isRebootPropertySet()) {
- break;
+ return;
}
- setFactoryResetProperty(true);
- long now = System.currentTimeMillis();
- setLastFactoryResetTimeMs(now);
- runnable = new Runnable() {
- @Override
- public void run() {
- try {
- RecoverySystem.rebootPromptAndWipeUserData(context, TAG);
- } catch (Throwable t) {
- logRescueException(level, failedPackage, t);
- }
- }
- };
- thread = new Thread(runnable);
- thread.start();
+ executeFactoryReset(context, level, failedPackage);
break;
}
@@ -500,6 +566,83 @@
}
}
+ private static void executeRescueLevelInternalNew(Context context, @RescueLevels int level,
+ @Nullable String failedPackage) throws Exception {
+ CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
+ level, levelToString(level));
+ switch (level) {
+ case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
+ // Temporary disable deviceConfig reset
+ // resetDeviceConfig(context, /*isScoped=*/true, failedPackage);
+ break;
+ case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
+ // Temporary disable deviceConfig reset
+ // resetDeviceConfig(context, /*isScoped=*/false, failedPackage);
+ break;
+ case RESCUE_LEVEL_WARM_REBOOT:
+ executeWarmReboot(context, level, failedPackage);
+ break;
+ case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS, level);
+ break;
+ case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_CHANGES, level);
+ break;
+ case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ resetAllSettingsIfNecessary(context, Settings.RESET_MODE_TRUSTED_DEFAULTS, level);
+ break;
+ case RESCUE_LEVEL_FACTORY_RESET:
+ // Before the completion of Reboot, if any crash happens then PackageWatchdog
+ // escalates to next level i.e. factory reset, as they happen in separate threads.
+ // Adding a check to prevent factory reset to execute before above reboot completes.
+ // Note: this reboot property is not persistent resets after reboot is completed.
+ if (isRebootPropertySet()) {
+ return;
+ }
+ executeFactoryReset(context, level, failedPackage);
+ break;
+ }
+ }
+
+ private static void executeWarmReboot(Context context, int level,
+ @Nullable String failedPackage) {
+ // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
+ // when device shutting down.
+ setRebootProperty(true);
+ Runnable runnable = () -> {
+ try {
+ PowerManager pm = context.getSystemService(PowerManager.class);
+ if (pm != null) {
+ pm.reboot(TAG);
+ }
+ } catch (Throwable t) {
+ logRescueException(level, failedPackage, t);
+ }
+ };
+ Thread thread = new Thread(runnable);
+ thread.start();
+ }
+
+ private static void executeFactoryReset(Context context, int level,
+ @Nullable String failedPackage) {
+ setFactoryResetProperty(true);
+ long now = System.currentTimeMillis();
+ setLastFactoryResetTimeMs(now);
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ RecoverySystem.rebootPromptAndWipeUserData(context, TAG);
+ } catch (Throwable t) {
+ logRescueException(level, failedPackage, t);
+ }
+ }
+ };
+ Thread thread = new Thread(runnable);
+ thread.start();
+ }
+
+
private static String getCompleteMessage(Throwable t) {
final StringBuilder builder = new StringBuilder();
builder.append(t.getMessage());
@@ -521,17 +664,38 @@
}
private static int mapRescueLevelToUserImpact(int rescueLevel) {
- switch(rescueLevel) {
- case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
- case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- case LEVEL_WARM_REBOOT:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
- case LEVEL_FACTORY_RESET:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
- default:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ if (Flags.recoverabilityDetection()) {
+ switch (rescueLevel) {
+ case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
+ case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_20;
+ case RESCUE_LEVEL_WARM_REBOOT:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
+ case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
+ case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_75;
+ case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_80;
+ case RESCUE_LEVEL_FACTORY_RESET:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
+ default:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ }
+ } else {
+ switch (rescueLevel) {
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
+ case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ case LEVEL_WARM_REBOOT:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
+ case LEVEL_FACTORY_RESET:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
+ default:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ }
}
}
@@ -548,7 +712,7 @@
final ContentResolver resolver = context.getContentResolver();
try {
Settings.Global.resetToDefaultsAsUser(resolver, null, mode,
- UserHandle.SYSTEM.getIdentifier());
+ UserHandle.SYSTEM.getIdentifier());
} catch (Exception e) {
res = new RuntimeException("Failed to reset global settings", e);
}
@@ -667,8 +831,13 @@
@FailureReasons int failureReason, int mitigationCount) {
if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
|| failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
- return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
+ if (Flags.recoverabilityDetection()) {
+ return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
+ mayPerformReboot(failedPackage), failedPackage));
+ } else {
+ return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
mayPerformReboot(failedPackage)));
+ }
} else {
return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
}
@@ -682,8 +851,10 @@
}
if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
|| failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
- final int level = getRescueLevel(mitigationCount,
- mayPerformReboot(failedPackage));
+ final int level = Flags.recoverabilityDetection() ? getRescueLevel(mitigationCount,
+ mayPerformReboot(failedPackage), failedPackage)
+ : getRescueLevel(mitigationCount,
+ mayPerformReboot(failedPackage));
executeRescueLevel(mContext,
failedPackage == null ? null : failedPackage.getPackageName(), level);
return true;
@@ -716,7 +887,12 @@
if (isDisabled()) {
return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
}
- return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
+ if (Flags.recoverabilityDetection()) {
+ return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
+ true, /*failedPackage=*/ null));
+ } else {
+ return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
+ }
}
@Override
@@ -725,8 +901,10 @@
return false;
}
boolean mayPerformReboot = !shouldThrottleReboot();
- executeRescueLevel(mContext, /*failedPackage=*/ null,
- getRescueLevel(mitigationCount, mayPerformReboot));
+ final int level = Flags.recoverabilityDetection() ? getRescueLevel(mitigationCount,
+ mayPerformReboot, /*failedPackage=*/ null)
+ : getRescueLevel(mitigationCount, mayPerformReboot);
+ executeRescueLevel(mContext, /*failedPackage=*/ null, level);
return true;
}
@@ -843,14 +1021,44 @@
}
private static String levelToString(int level) {
- switch (level) {
- case LEVEL_NONE: return "NONE";
- case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
- case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: return "RESET_SETTINGS_UNTRUSTED_CHANGES";
- case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: return "RESET_SETTINGS_TRUSTED_DEFAULTS";
- case LEVEL_WARM_REBOOT: return "WARM_REBOOT";
- case LEVEL_FACTORY_RESET: return "FACTORY_RESET";
- default: return Integer.toString(level);
+ if (Flags.recoverabilityDetection()) {
+ switch (level) {
+ case RESCUE_LEVEL_NONE:
+ return "NONE";
+ case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
+ return "SCOPED_DEVICE_CONFIG_RESET";
+ case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
+ return "ALL_DEVICE_CONFIG_RESET";
+ case RESCUE_LEVEL_WARM_REBOOT:
+ return "WARM_REBOOT";
+ case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
+ case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ return "RESET_SETTINGS_UNTRUSTED_CHANGES";
+ case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ return "RESET_SETTINGS_TRUSTED_DEFAULTS";
+ case RESCUE_LEVEL_FACTORY_RESET:
+ return "FACTORY_RESET";
+ default:
+ return Integer.toString(level);
+ }
+ } else {
+ switch (level) {
+ case LEVEL_NONE:
+ return "NONE";
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ return "RESET_SETTINGS_UNTRUSTED_CHANGES";
+ case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ return "RESET_SETTINGS_TRUSTED_DEFAULTS";
+ case LEVEL_WARM_REBOOT:
+ return "WARM_REBOOT";
+ case LEVEL_FACTORY_RESET:
+ return "FACTORY_RESET";
+ default:
+ return Integer.toString(level);
+ }
}
}
}
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 0fb9327..93f26ae 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -69,7 +69,7 @@
*
* @hide
*/
-final class RollbackPackageHealthObserver implements PackageHealthObserver {
+public final class RollbackPackageHealthObserver implements PackageHealthObserver {
private static final String TAG = "RollbackPackageHealthObserver";
private static final String NAME = "rollback-observer";
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
@@ -89,7 +89,7 @@
private boolean mTwoPhaseRollbackEnabled;
@VisibleForTesting
- RollbackPackageHealthObserver(Context context, ApexManager apexManager) {
+ public RollbackPackageHealthObserver(Context context, ApexManager apexManager) {
mContext = context;
HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
handlerThread.start();
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 99a9409..d13d86f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -305,10 +305,14 @@
modifier = Modifier.fillMaxWidth()
) {
if (leftButton != null) {
- leftButton()
+ Box(modifier = Modifier.wrapContentSize().weight(1f, fill = false)) {
+ leftButton()
+ }
}
if (rightButton != null) {
- rightButton()
+ Box(modifier = Modifier.wrapContentSize().weight(1f, fill = false)) {
+ rightButton()
+ }
}
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index a46e358..3fb91522 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -17,7 +17,6 @@
package com.android.credentialmanager.common.ui
import android.content.Context
-import android.content.res.Configuration
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
import com.android.credentialmanager.model.get.CredentialEntryInfo
@@ -27,10 +26,12 @@
class RemoteViewsFactory {
companion object {
- private const val setAdjustViewBoundsMethodName = "setAdjustViewBounds"
- private const val setMaxHeightMethodName = "setMaxHeight"
- private const val setBackgroundResourceMethodName = "setBackgroundResource"
- private const val bulletPoint = "\u2022"
+ private const val SET_ADJUST_VIEW_BOUNDS_METHOD_NAME = "setAdjustViewBounds"
+ private const val SET_MAX_HEIGHT_METHOD_NAME = "setMaxHeight"
+ private const val SET_BACKGROUND_RESOURCE_METHOD_NAME = "setBackgroundResource"
+ private const val BULLET_POINT = "\u2022"
+ // TODO(jbabs): RemoteViews#setViewPadding renders this as 8dp on the display. Debug why.
+ private const val END_ITEMS_PADDING = 28
fun createDropdownPresentation(
context: Context,
@@ -50,18 +51,18 @@
val secondaryText =
if (credentialEntryInfo.displayName != null
&& (credentialEntryInfo.displayName != credentialEntryInfo.userName))
- (credentialEntryInfo.userName + " " + bulletPoint + " "
+ (credentialEntryInfo.userName + " " + BULLET_POINT + " "
+ credentialEntryInfo.credentialTypeDisplayName
- + " " + bulletPoint + " " + credentialEntryInfo.providerDisplayName)
- else (credentialEntryInfo.credentialTypeDisplayName + " " + bulletPoint + " "
+ + " " + BULLET_POINT + " " + credentialEntryInfo.providerDisplayName)
+ else (credentialEntryInfo.credentialTypeDisplayName + " " + BULLET_POINT + " "
+ credentialEntryInfo.providerDisplayName)
remoteViews.setTextViewText(android.R.id.text2, secondaryText)
remoteViews.setImageViewIcon(android.R.id.icon1, icon);
remoteViews.setBoolean(
- android.R.id.icon1, setAdjustViewBoundsMethodName, true);
+ android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true);
remoteViews.setInt(
android.R.id.icon1,
- setMaxHeightMethodName,
+ SET_MAX_HEIGHT_METHOD_NAME,
context.resources.getDimensionPixelSize(
com.android.credentialmanager.R.dimen.autofill_icon_size));
remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo
@@ -71,11 +72,11 @@
com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one else
com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_middle
remoteViews.setInt(
- android.R.id.content, setBackgroundResourceMethodName, drawableId);
+ android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId);
if (isFirstEntry) remoteViews.setViewPadding(
com.android.credentialmanager.R.id.credential_card,
/* left=*/0,
- /* top=*/8,
+ /* top=*/END_ITEMS_PADDING,
/* right=*/0,
/* bottom=*/0)
if (isLastEntry) remoteViews.setViewPadding(
@@ -83,7 +84,7 @@
/*left=*/0,
/* top=*/0,
/* right=*/0,
- /* bottom=*/8)
+ /* bottom=*/END_ITEMS_PADDING)
return remoteViews
}
@@ -95,16 +96,16 @@
com.android.credentialmanager
.R.string.dropdown_presentation_more_sign_in_options_text))
remoteViews.setBoolean(
- android.R.id.icon1, setAdjustViewBoundsMethodName, true);
+ android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true);
remoteViews.setInt(
android.R.id.icon1,
- setMaxHeightMethodName,
+ SET_MAX_HEIGHT_METHOD_NAME,
context.resources.getDimensionPixelSize(
com.android.credentialmanager.R.dimen.autofill_icon_size));
val drawableId =
com.android.credentialmanager.R.drawable.more_options_list_item
remoteViews.setInt(
- android.R.id.content, setBackgroundResourceMethodName, drawableId);
+ android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId);
return remoteViews
}
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm b/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
index 3f5e894..f2843ed 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
@@ -18,6 +18,8 @@
type OVERLAY
+map key 86 PLUS
+
### ROW 1
key GRAVE {
@@ -42,13 +44,14 @@
key 3 {
label: '3'
base: '3'
- shift: '\u2166'
+ shift: '\u2116'
}
key 4 {
label: '4'
base: '4'
shift: ';'
+ ralt: '\u20bc'
}
key 5 {
@@ -61,14 +64,12 @@
label: '6'
base: '6'
shift: ':'
- shift+ralt: '^'
}
key 7 {
label: '7'
base: '7'
shift: '?'
- ralt: '&'
}
key 8 {
@@ -176,21 +177,21 @@
key LEFT_BRACKET {
label: '\u00d6'
base: '\u00f6'
- shift: '\u00d6'
+ shift, capslock: '\u00d6'
shift+capslock: '\u00f6'
}
key RIGHT_BRACKET {
label: '\u011e'
base: '\u011f'
- shift: '\u011e'
+ shift, capslock: '\u011e'
shift+capslock: '\u011f'
}
key BACKSLASH {
label: '\\'
base: '\\'
- shift: '|'
+ shift: '/'
}
### ROW 3
@@ -261,19 +262,25 @@
key SEMICOLON {
label: 'I'
base: '\u0131'
- shift: 'I'
+ shift, capslock: 'I'
shift+capslock: '\u0131'
}
key APOSTROPHE {
label: '\u018f'
base: '\u0259'
- shift: '\u018f'
+ shift, capslock: '\u018f'
shift+capslock: '\u0259'
}
### ROW 4
+key PLUS {
+ label: '\\'
+ base: '\\'
+ shift: '/'
+}
+
key Z {
label: 'Z'
base: 'z'
@@ -326,14 +333,14 @@
key COMMA {
label: '\u00c7'
base: '\u00e7'
- shift: '\u00c7'
+ shift, capslock: '\u00c7'
shift+capslock: '\u00e7'
}
key PERIOD {
label: '\u015e'
base: '\u015f'
- shift: '\u015e'
+ shift, capslock: '\u015e'
shift+capslock: '\u015f'
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 6eb2dd0..8cafe5f 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -688,6 +688,7 @@
Settings.Secure.DEVICE_PAIRED,
Settings.Secure.DIALER_DEFAULT_APPLICATION,
Settings.Secure.DISABLED_PRINT_SERVICES,
+ Settings.Secure.DISABLE_SECURE_WINDOWS,
Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
Settings.Secure.DOCKED_CLOCK_FACE,
Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 31d3fa0..9f02201 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -32,12 +32,12 @@
import com.android.compose.animation.scene.SceneScope
import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
@@ -69,7 +69,7 @@
) {
@Composable
fun SceneScope.LockIcon(modifier: Modifier = Modifier) {
- if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
+ if (!KeyguardBottomAreaRefactor.isEnabled && !DeviceEntryUdfpsRefactor.isEnabled) {
return
}
@@ -96,7 +96,7 @@
)
}
} else {
- // keyguardBottomAreaRefactor()
+ // KeyguardBottomAreaRefactor.isEnabled
LockIconView(context, null).apply {
id = R.id.lock_icon_view
lockIconViewController.get().setLockIconView(this)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 525ad16..6b86a48 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -20,13 +20,11 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.notifications.ui.composable.NotificationStack
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
-import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackViewBinder
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
@@ -37,17 +35,15 @@
@Inject
constructor(
private val viewModel: NotificationsPlaceholderViewModel,
- sceneContainerFlags: SceneContainerFlags,
sharedNotificationContainer: SharedNotificationContainer,
sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
stackScrollLayout: NotificationStackScrollLayout,
sharedNotificationContainerBinder: SharedNotificationContainerBinder,
- notificationStackViewBinder: NotificationStackViewBinder,
) {
init {
- if (!migrateClocksToBlueprint()) {
- throw IllegalStateException("this requires migrateClocksToBlueprint()")
+ if (!MigrateClocksToBlueprint.isEnabled) {
+ throw IllegalStateException("this requires MigrateClocksToBlueprint.isEnabled")
}
// This scene container section moves the NSSL to the SharedNotificationContainer.
// This also requires that SharedNotificationContainer gets moved to the
@@ -65,10 +61,6 @@
sharedNotificationContainer,
sharedNotificationContainerViewModel,
)
-
- if (sceneContainerFlags.isEnabled()) {
- notificationStackViewBinder.bindWhileAttached()
- }
}
@Composable
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
index f24723a..97a10e6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
@@ -33,7 +33,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
/** Test [DataSaverDialogDelegate]. */
@@ -69,7 +68,7 @@
fun delegateSetsDialogTitleCorrectly() {
val expectedResId = R.string.data_saver_enable_title
- dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+ dataSaverDialogDelegate.beforeCreate(sysuiDialog, null)
verify(sysuiDialog).setTitle(eq(expectedResId))
}
@@ -78,7 +77,7 @@
fun delegateSetsDialogMessageCorrectly() {
val expectedResId = R.string.data_saver_description
- dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+ dataSaverDialogDelegate.beforeCreate(sysuiDialog, null)
verify(sysuiDialog).setMessage(expectedResId)
}
@@ -87,7 +86,7 @@
fun delegateSetsDialogPositiveButtonCorrectly() {
val expectedResId = R.string.data_saver_enable_button
- dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+ dataSaverDialogDelegate.beforeCreate(sysuiDialog, null)
verify(sysuiDialog).setPositiveButton(eq(expectedResId), any())
}
@@ -96,7 +95,7 @@
fun delegateSetsDialogCancelButtonCorrectly() {
val expectedResId = R.string.cancel
- dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+ dataSaverDialogDelegate.beforeCreate(sysuiDialog, null)
verify(sysuiDialog).setNeutralButton(eq(expectedResId), eq(null))
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileDataInteractorTest.kt
new file mode 100644
index 0000000..8651300
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileDataInteractorTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.work.domain.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.work.domain.model.WorkModeTileModel
+import com.android.systemui.utils.leaks.FakeManagedProfileController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class WorkModeTileDataInteractorTest : SysuiTestCase() {
+ private val controller = FakeManagedProfileController(LeakCheck())
+ private val underTest: WorkModeTileDataInteractor = WorkModeTileDataInteractor(controller)
+
+ @Test
+ fun availability_matchesControllerHasActiveProfiles() = runTest {
+ val availability by collectLastValue(underTest.availability(TEST_USER))
+
+ assertThat(availability).isFalse()
+
+ controller.setHasActiveProfile(true)
+ assertThat(availability).isTrue()
+
+ controller.setHasActiveProfile(false)
+ assertThat(availability).isFalse()
+ }
+
+ @Test
+ fun tileData_whenHasActiveProfile_matchesControllerIsEnabled() = runTest {
+ controller.setHasActiveProfile(true)
+ val data by
+ collectLastValue(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ assertThat(data).isInstanceOf(WorkModeTileModel.HasActiveProfile::class.java)
+ assertThat((data as WorkModeTileModel.HasActiveProfile).isEnabled).isFalse()
+
+ controller.isWorkModeEnabled = true
+ assertThat(data).isInstanceOf(WorkModeTileModel.HasActiveProfile::class.java)
+ assertThat((data as WorkModeTileModel.HasActiveProfile).isEnabled).isTrue()
+
+ controller.isWorkModeEnabled = false
+ assertThat(data).isInstanceOf(WorkModeTileModel.HasActiveProfile::class.java)
+ assertThat((data as WorkModeTileModel.HasActiveProfile).isEnabled).isFalse()
+ }
+
+ @Test
+ fun tileData_matchesControllerHasActiveProfile() = runTest {
+ val data by
+ collectLastValue(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ assertThat(data).isInstanceOf(WorkModeTileModel.NoActiveProfile::class.java)
+
+ controller.setHasActiveProfile(true)
+ assertThat(data).isInstanceOf(WorkModeTileModel.HasActiveProfile::class.java)
+
+ controller.setHasActiveProfile(false)
+ assertThat(data).isInstanceOf(WorkModeTileModel.NoActiveProfile::class.java)
+ }
+
+ private companion object {
+ val TEST_USER = UserHandle.of(1)!!
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..8a63e2c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractorTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.work.domain.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.work.domain.model.WorkModeTileModel
+import com.android.systemui.utils.leaks.FakeManagedProfileController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class WorkModeTileUserActionInteractorTest : SysuiTestCase() {
+
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val profileController = FakeManagedProfileController(LeakCheck())
+
+ private val underTest =
+ WorkModeTileUserActionInteractor(
+ profileController,
+ inputHandler,
+ )
+
+ @Test
+ fun handleClickWhenEnabled() = runTest {
+ val wasEnabled = true
+ profileController.isWorkModeEnabled = wasEnabled
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(WorkModeTileModel.HasActiveProfile(wasEnabled))
+ )
+
+ assertThat(profileController.isWorkModeEnabled).isEqualTo(!wasEnabled)
+ }
+
+ @Test
+ fun handleClickWhenDisabled() = runTest {
+ val wasEnabled = false
+ profileController.isWorkModeEnabled = wasEnabled
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(WorkModeTileModel.HasActiveProfile(wasEnabled))
+ )
+
+ assertThat(profileController.isWorkModeEnabled).isEqualTo(!wasEnabled)
+ }
+
+ @Test
+ fun handleClickWhenUnavailable() = runTest {
+ val wasEnabled = false
+ profileController.isWorkModeEnabled = wasEnabled
+
+ underTest.handleInput(QSTileInputTestKtx.click(WorkModeTileModel.NoActiveProfile))
+
+ assertThat(profileController.isWorkModeEnabled).isEqualTo(wasEnabled)
+ }
+
+ @Test
+ fun handleLongClickWhenDisabled() = runTest {
+ val enabled = false
+
+ underTest.handleInput(
+ QSTileInputTestKtx.longClick(WorkModeTileModel.HasActiveProfile(enabled))
+ )
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_MANAGED_PROFILE_SETTINGS)
+ }
+ }
+
+ @Test
+ fun handleLongClickWhenEnabled() = runTest {
+ val enabled = true
+
+ underTest.handleInput(
+ QSTileInputTestKtx.longClick(WorkModeTileModel.HasActiveProfile(enabled))
+ )
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_MANAGED_PROFILE_SETTINGS)
+ }
+ }
+
+ @Test
+ fun handleLongClickWhenUnavailable() = runTest {
+ underTest.handleInput(QSTileInputTestKtx.longClick(WorkModeTileModel.NoActiveProfile))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 8a2245d..48271de 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -31,7 +31,6 @@
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.customization.R
import com.android.systemui.dagger.qualifiers.Background
@@ -39,6 +38,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags.REGION_SAMPLING
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
@@ -328,7 +328,7 @@
object : KeyguardUpdateMonitorCallback() {
override fun onKeyguardVisibilityChanged(visible: Boolean) {
isKeyguardVisible = visible
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
if (!isKeyguardVisible) {
clock?.run {
smallClock.animations.doze(if (isDozing) 1f else 0f)
@@ -368,7 +368,7 @@
}
private fun refreshTime() {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
@@ -427,7 +427,7 @@
parent.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
listenForDozing(this)
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
listenForDozeAmountTransition(this)
listenForAnyStateToAodTransition(this)
} else {
diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
index 630610d..df77a58 100644
--- a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
@@ -30,7 +30,7 @@
import android.widget.FrameLayout
import android.widget.FrameLayout.LayoutParams
import com.android.keyguard.dagger.KeyguardStatusViewComponent
-import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.res.R
@@ -95,7 +95,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
onCreateV2()
} else {
onCreate()
@@ -132,7 +132,7 @@
}
override fun onAttachedToWindow() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
clockRegistry.registerClockChangeListener(clockChangedListener)
clockEventController.registerListeners(clock!!)
@@ -141,7 +141,7 @@
}
override fun onDetachedFromWindow() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
clockEventController.unregisterListeners()
clockRegistry.unregisterClockChangeListener(clockChangedListener)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 28013c6..4a96e9e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -3,7 +3,6 @@
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_X_CLOCK_DESIGN;
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_DESIGN;
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_SIZE;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -23,6 +22,7 @@
import com.android.app.animation.Interpolators;
import com.android.keyguard.dagger.KeyguardStatusViewScope;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.plugins.clocks.ClockController;
@@ -192,7 +192,7 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
mStatusArea = findViewById(R.id.keyguard_status_area);
@@ -266,7 +266,7 @@
}
void updateClockTargetRegions() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
if (mClock != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 86f64f8..5b8eb9d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -21,7 +21,6 @@
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
import static com.android.systemui.Flags.smartspaceRelocateToBottom;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -45,6 +44,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
@@ -202,7 +202,7 @@
mClockChangedListener = new ClockRegistry.ClockChangeListener() {
@Override
public void onCurrentClockChanged() {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
setClock(mClockRegistry.createCurrentClock());
}
}
@@ -245,7 +245,7 @@
protected void onInit() {
mKeyguardSliceViewController.init();
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
}
@@ -340,7 +340,7 @@
addDateWeatherView();
}
}
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
setDateWeatherVisibility();
setWeatherVisibility();
}
@@ -348,7 +348,7 @@
}
int getNotificationIconAreaHeight() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return 0;
} else if (NotificationIconContainerRefactor.isEnabled()) {
return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0;
@@ -391,7 +391,7 @@
}
private void addDateWeatherView() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
mDateWeatherView = (ViewGroup) mSmartspaceController.buildAndConnectDateView(mView);
@@ -407,7 +407,7 @@
}
private void addWeatherView() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
@@ -420,7 +420,7 @@
}
private void addSmartspaceView() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
@@ -528,7 +528,7 @@
*/
void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
x, props, animate);
PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
@@ -554,7 +554,7 @@
return 0;
}
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return 0;
}
@@ -589,14 +589,14 @@
}
boolean isClockTopAligned() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return mKeyguardClockInteractor.getClockSize().getValue() == LARGE;
}
return mLargeClockFrame.getVisibility() != View.VISIBLE;
}
private void updateAodIcons() {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
NotificationIconContainer nic = (NotificationIconContainer)
mView.findViewById(
com.android.systemui.res.R.id.left_aligned_notification_icon_container);
@@ -616,7 +616,7 @@
}
private void setClock(ClockController clock) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
if (clock != null && mLogBuffer != null) {
@@ -630,7 +630,7 @@
@Nullable
public ClockController getClock() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return mKeyguardClockInteractor.getCurrentClock().getValue();
} else {
return mClockEventController.getClock();
@@ -642,7 +642,7 @@
}
private void updateDoubleLineClock() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
mCanShowDoubleLineClock = mSecureSettings.getIntForUser(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 7f9ae5e..603a47e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -20,7 +20,6 @@
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.animation.Animator;
@@ -52,6 +51,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ViewHierarchyAnimator;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.plugins.clocks.ClockController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
@@ -223,7 +223,7 @@
}
mDumpManager.registerDumpable(getInstanceName(), this);
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
startCoroutines(EmptyCoroutineContext.INSTANCE);
mView.setVisibility(View.GONE);
}
@@ -250,7 +250,7 @@
@Override
protected void onViewAttached() {
mStatusArea = mView.findViewById(R.id.keyguard_status_area);
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
@@ -261,7 +261,7 @@
@Override
protected void onViewDetached() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
@@ -485,7 +485,7 @@
boolean splitShadeEnabled,
boolean shouldBeCentered,
boolean animate) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
} else {
mKeyguardClockSwitchController.setSplitShadeCentered(
@@ -503,7 +503,7 @@
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(layout);
int guideline;
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
guideline = R.id.split_shade_guideline;
} else {
guideline = R.id.qs_edge_guideline;
@@ -548,7 +548,7 @@
&& clock.getLargeClock().getConfig().getHasCustomPositionUpdatedAnimation();
// When migrateClocksToBlueprint is on, customized clock animation is conducted in
// KeyguardClockViewBinder
- if (customClockAnimation && !migrateClocksToBlueprint()) {
+ if (customClockAnimation && !MigrateClocksToBlueprint.isEnabled()) {
// Find the clock, so we can exclude it from this transition.
FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index f5a6cb3..fd8b6d5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -24,6 +23,7 @@
import android.view.View;
import com.android.app.animation.Interpolators;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.statusbar.StatusBarState;
@@ -88,7 +88,7 @@
boolean keyguardFadingAway,
boolean goingToFullShade,
int oldStatusBarState) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
log("Ignoring KeyguardVisibilityelper, migrateClocksToBlueprint flag on");
return;
}
@@ -113,7 +113,7 @@
animProps.setDelay(0).setDuration(160);
log("goingToFullShade && !keyguardFadingAway");
}
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
log("Using LockscreenToGoneTransition 1");
} else {
PropertyAnimator.setProperty(
@@ -171,7 +171,7 @@
animProps,
true /* animate */);
} else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
log("Using GoneToAodTransition");
mKeyguardViewVisibilityAnimating = false;
} else {
@@ -187,7 +187,7 @@
mView.setVisibility(View.VISIBLE);
}
} else {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
log("Using LockscreenToGoneTransition 2");
} else {
log("Direct set Visibility to GONE");
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 039a2e5..8f1a5f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -22,8 +22,6 @@
import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
import static com.android.keyguard.LockIconView.ICON_LOCK;
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
-import static com.android.systemui.Flags.keyguardBottomAreaRefactor;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
@@ -68,6 +66,8 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -453,7 +453,7 @@
private void updateLockIconLocation() {
final float scaleFactor = mAuthController.getScaleFactor();
final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
- if (keyguardBottomAreaRefactor() || migrateClocksToBlueprint()) {
+ if (KeyguardBottomAreaRefactor.isEnabled() || MigrateClocksToBlueprint.isEnabled()) {
mView.getLockIcon().setPadding(scaledPadding, scaledPadding, scaledPadding,
scaledPadding);
} else {
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index a0f15ef..781f6dd 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -16,8 +16,6 @@
package com.android.keyguard.dagger;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
-
import android.content.Context;
import android.content.res.Resources;
import android.view.LayoutInflater;
@@ -28,6 +26,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.clocks.ClockMessageBuffers;
import com.android.systemui.res.R;
@@ -70,7 +69,7 @@
layoutInflater,
resources,
featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION),
- migrateClocksToBlueprint()),
+ MigrateClocksToBlueprint.isEnabled()),
context.getString(R.string.lockscreen_clock_id_fallback),
clockBuffers,
/* keepAllLoaded = */ false,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 298da13..1bcee74 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -23,13 +23,11 @@
import com.android.server.notification.Flags.politeNotifications
import com.android.server.notification.Flags.vibrateWhileUnlocked
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
-import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.communalHub
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -58,11 +56,11 @@
SceneContainerFlag.getMainStaticFlag() dependsOn MIGRATE_KEYGUARD_STATUS_BAR_VIEW
// ComposeLockscreen dependencies
- ComposeLockscreen.token dependsOn keyguardBottomAreaRefactor
- ComposeLockscreen.token dependsOn migrateClocksToBlueprint
+ ComposeLockscreen.token dependsOn KeyguardBottomAreaRefactor.token
+ ComposeLockscreen.token dependsOn MigrateClocksToBlueprint.token
// CommunalHub dependencies
- communalHub dependsOn migrateClocksToBlueprint
+ communalHub dependsOn MigrateClocksToBlueprint.token
}
private inline val politeNotifications
@@ -71,10 +69,6 @@
get() = FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
private inline val vibrateWhileUnlockedToken: FlagToken
get() = FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
- private inline val keyguardBottomAreaRefactor
- get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
- private inline val migrateClocksToBlueprint
- get() = FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint())
private inline val communalHub
get() = FlagToken(FLAG_COMMUNAL_HUB, communalHub())
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardBottomAreaRefactor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardBottomAreaRefactor.kt
new file mode 100644
index 0000000..779b27b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardBottomAreaRefactor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the keyguard bottom area refactor flag. */
+@Suppress("NOTHING_TO_INLINE")
+object KeyguardBottomAreaRefactor {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.keyguardBottomAreaRefactor()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 5565ee2..d9d7470 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -36,7 +36,6 @@
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardStatusViewComponent
import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
@@ -166,7 +165,7 @@
fun bindIndicationArea() {
indicationAreaHandle?.dispose()
- if (!keyguardBottomAreaRefactor()) {
+ if (!KeyguardBottomAreaRefactor.isEnabled) {
keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
keyguardRootView.removeView(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3b34750..f700e03 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -40,7 +40,6 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground;
import static com.android.systemui.Flags.refactorGetCurrentUser;
import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
@@ -3404,7 +3403,8 @@
}
// Ensure that keyguard becomes visible if the going away animation is canceled
- if (showKeyguard && !KeyguardWmStateRefactor.isEnabled() && migrateClocksToBlueprint()) {
+ if (showKeyguard && !KeyguardWmStateRefactor.isEnabled()
+ && MigrateClocksToBlueprint.isEnabled()) {
mKeyguardInteractor.showKeyguard();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/MigrateClocksToBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/MigrateClocksToBlueprint.kt
new file mode 100644
index 0000000..5a2943b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/MigrateClocksToBlueprint.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the migrate clocks to blueprint flag. */
+@Suppress("NOTHING_TO_INLINE")
+object MigrateClocksToBlueprint {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.migrateClocksToBlueprint()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
index b9ec58c..53f2416 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -39,7 +39,7 @@
/** The position of the keyguard clock. */
private val _clockPosition = MutableStateFlow(Position(0, 0))
/** See [ClockSection] */
- @Deprecated("with migrateClocksToBlueprint()")
+ @Deprecated("with MigrateClocksToBlueprint.isEnabled")
val clockPosition: Flow<Position> = _clockPosition.asStateFlow()
fun setClockPosition(x: Int, y: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 89148b0..7e3ddf9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -26,9 +26,9 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
@@ -105,7 +105,7 @@
var transition =
if (
- !keyguardBottomAreaRefactor() &&
+ !KeyguardBottomAreaRefactor.isEnabled &&
prevBluePrint != null &&
prevBluePrint != blueprint
) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index fa1fe5e..6255f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -28,7 +28,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.keyguard.KeyguardClockSwitch.SMALL
-import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
@@ -59,7 +59,7 @@
keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
- if (!migrateClocksToBlueprint()) return@launch
+ if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.currentClock.collect { currentClock ->
cleanupClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
addClockViews(currentClock, keyguardRootView)
@@ -68,14 +68,14 @@
}
}
launch {
- if (!migrateClocksToBlueprint()) return@launch
+ if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.clockSize.collect {
updateBurnInLayer(keyguardRootView, viewModel)
blueprintInteractor.refreshBlueprint(Type.ClockSize)
}
}
launch {
- if (!migrateClocksToBlueprint()) return@launch
+ if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
viewModel.currentClock.value?.let {
// Weather clock also has hasCustomPositionUpdatedAnimation as true
@@ -92,7 +92,7 @@
}
}
launch {
- if (!migrateClocksToBlueprint()) return@launch
+ if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.isAodIconsVisible.collect { isAodIconsVisible ->
viewModel.currentClock.value?.let {
// Weather clock also has hasCustomPositionUpdatedAnimation as true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 841f52d..267d68e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -22,8 +22,8 @@
import android.widget.TextView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
@@ -69,7 +69,10 @@
launch {
// Do not independently apply alpha, as [KeyguardRootViewModel] should work
// for this and all its children
- if (!(migrateClocksToBlueprint() || keyguardBottomAreaRefactor())) {
+ if (
+ !(MigrateClocksToBlueprint.isEnabled ||
+ KeyguardBottomAreaRefactor.isEnabled)
+ ) {
viewModel.alpha.collect { alpha -> view.alpha = alpha }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index d0246a8..0ed42ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -36,8 +36,6 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.Flags.newAodTransition
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
@@ -45,6 +43,8 @@
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -109,7 +109,7 @@
val endButton = R.id.end_button
val lockIcon = R.id.lock_icon_view
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
view.setOnTouchListener { _, event ->
if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
viewModel.setRootViewLastTapPosition(Point(event.x.toInt(), event.y.toInt()))
@@ -143,11 +143,13 @@
}
}
- if (keyguardBottomAreaRefactor() || DeviceEntryUdfpsRefactor.isEnabled) {
+ if (
+ KeyguardBottomAreaRefactor.isEnabled || DeviceEntryUdfpsRefactor.isEnabled
+ ) {
launch {
viewModel.alpha(viewState).collect { alpha ->
view.alpha = alpha
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
childViews[statusViewId]?.alpha = alpha
childViews[burnInLayerId]?.alpha = alpha
}
@@ -155,7 +157,7 @@
}
}
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
launch {
viewModel.burnInLayerVisibility.collect { visibility ->
childViews[burnInLayerId]?.visibility = visibility
@@ -342,13 +344,13 @@
}
}
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
burnInParams.update { current ->
current.copy(clockControllerProvider = clockControllerProvider)
}
}
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
burnInParams.update { current ->
current.copy(translationY = { childViews[burnInLayerId]?.translationY })
}
@@ -439,7 +441,7 @@
burnInParams.update { current ->
current.copy(
minViewY =
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
// To ensure burn-in doesn't enroach the top inset, get the min top Y
childViews.entries.fold(Int.MAX_VALUE) { currentMin, (viewId, view) ->
min(
@@ -472,7 +474,7 @@
configuration: ConfigurationState,
screenOffAnimationController: ScreenOffAnimationController,
) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
throw IllegalStateException("should only be called in legacy code paths")
}
if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return
@@ -503,7 +505,7 @@
}
when {
!isVisible.isAnimating -> {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
translationY = 0f
}
visibility =
@@ -553,7 +555,7 @@
animatorListener: Animator.AnimatorListener,
) {
if (animate) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
translationY = -iconAppearTranslation.toFloat()
}
alpha = 0f
@@ -561,19 +563,19 @@
.alpha(1f)
.setInterpolator(Interpolators.LINEAR)
.setDuration(AOD_ICONS_APPEAR_DURATION)
- .apply { if (migrateClocksToBlueprint()) animateInIconTranslation() }
+ .apply { if (MigrateClocksToBlueprint.isEnabled) animateInIconTranslation() }
.setListener(animatorListener)
.start()
} else {
alpha = 1.0f
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
translationY = 0f
}
}
}
private fun View.animateInIconTranslation() {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 4d0a25f..9aebf66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -21,7 +21,7 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
@@ -43,7 +43,7 @@
keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
- if (!migrateClocksToBlueprint()) return@launch
+ if (!MigrateClocksToBlueprint.isEnabled) return@launch
clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay
->
updateDateWeatherToBurnInLayer(
@@ -62,7 +62,7 @@
}
launch {
- if (!migrateClocksToBlueprint()) return@launch
+ if (!MigrateClocksToBlueprint.isEnabled) return@launch
smartspaceViewModel.bcSmartspaceVisibility.collect {
updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel)
blueprintInteractor.refreshBlueprint(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index f60da0e..6f39192 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -50,8 +50,6 @@
import androidx.core.view.isInvisible
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -61,6 +59,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -186,7 +186,7 @@
coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job())
disposables += DisposableHandle { coroutineScope.cancel() }
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
quickAffordancesCombinedViewModel.enablePreviewMode(
initiallySelectedSlotId =
bundle.getString(
@@ -204,7 +204,7 @@
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
}
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance
}
runBlocking(mainDispatcher) {
@@ -231,7 +231,7 @@
setupKeyguardRootView(previewContext, rootView)
- if (!keyguardBottomAreaRefactor()) {
+ if (!KeyguardBottomAreaRefactor.isEnabled) {
setUpBottomArea(rootView)
}
@@ -275,7 +275,7 @@
}
fun onSlotSelected(slotId: String) {
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
} else {
bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
@@ -286,7 +286,7 @@
isDestroyed = true
lockscreenSmartspaceController.disconnect()
disposables.dispose()
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
shortcutsBindings.forEach { it.destroy() }
}
}
@@ -372,7 +372,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) {
val keyguardRootView = KeyguardRootView(previewContext, null)
- if (!keyguardBottomAreaRefactor()) {
+ if (!KeyguardBottomAreaRefactor.isEnabled) {
disposables +=
KeyguardRootViewBinder.bind(
keyguardRootView,
@@ -397,15 +397,18 @@
),
)
- setUpUdfps(previewContext, if (migrateClocksToBlueprint()) keyguardRootView else rootView)
+ setUpUdfps(
+ previewContext,
+ if (MigrateClocksToBlueprint.isEnabled) keyguardRootView else rootView
+ )
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
setupShortcuts(keyguardRootView)
}
if (!shouldHideClock) {
setUpClock(previewContext, rootView)
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
KeyguardPreviewClockViewBinder.bind(
context,
displayId,
@@ -482,7 +485,7 @@
) as View
// Place the UDFPS view in the proper sensor location
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
finger.id = R.id.lock_icon_view
parentView.addView(finger)
val cs = ConstraintSet()
@@ -509,7 +512,7 @@
private fun setUpClock(previewContext: Context, parentView: ViewGroup) {
val resources = parentView.resources
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
largeClockHostView = FrameLayout(previewContext)
largeClockHostView.layoutParams =
FrameLayout.LayoutParams(
@@ -547,7 +550,7 @@
}
// TODO (b/283465254): Move the listeners to KeyguardClockRepository
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
val clockChangeListener =
object : ClockRegistry.ClockChangeListener {
override fun onCurrentClockChanged() {
@@ -581,7 +584,7 @@
)
disposables += DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) }
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
val layoutChangeListener =
View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
if (clockController.clock !is DefaultClockController) {
@@ -629,7 +632,7 @@
}
}
private fun onClockChanged() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
return
}
coroutineScope.launch {
@@ -676,7 +679,7 @@
}
private fun updateLargeClock(clock: ClockController) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
return
}
clock.largeClock.events.onTargetRegionChanged(
@@ -690,7 +693,7 @@
}
private fun updateSmallClock(clock: ClockController) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
return
}
clock.smallClock.events.onTargetRegionChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index cd46d6c..2e96638 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -25,9 +25,9 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -49,14 +49,14 @@
private val vibratorHelper: VibratorHelper,
) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
addLeftShortcut(constraintLayout)
addRightShortcut(constraintLayout)
}
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
leftShortcutHandle =
KeyguardQuickAffordanceViewBinder.bind(
constraintLayout.requireViewById(R.id.start_button),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 88ce9dc..d639978 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -23,7 +23,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -47,7 +47,7 @@
}
}
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
@@ -62,14 +62,14 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
clockViewModel.burnInLayer = burnInLayer
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 3d9c04e..2832e9d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -26,8 +26,8 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
@@ -58,7 +58,7 @@
private lateinit var nic: NotificationIconContainer
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
nic =
@@ -77,7 +77,7 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
@@ -98,7 +98,7 @@
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
val bottomMargin =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 7847c1c..881467f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -30,8 +30,8 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.constraintlayout.widget.ConstraintSet.VISIBLE
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
-import com.android.systemui.Flags
import com.android.systemui.customization.R as customizationR
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
@@ -70,7 +70,7 @@
override fun addViews(constraintLayout: ConstraintLayout) {}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!Flags.migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
KeyguardClockViewBinder.bind(
@@ -83,7 +83,7 @@
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!Flags.migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
keyguardClockViewModel.currentClock.value?.let { clock ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 8fd8bec..4c846e4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -28,13 +28,12 @@
import androidx.constraintlayout.widget.ConstraintSet
import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
@@ -72,8 +71,8 @@
override fun addViews(constraintLayout: ConstraintLayout) {
if (
- !keyguardBottomAreaRefactor() &&
- !migrateClocksToBlueprint() &&
+ !KeyguardBottomAreaRefactor.isEnabled &&
+ !DeviceEntryUdfpsRefactor.isEnabled &&
!DeviceEntryUdfpsRefactor.isEnabled
) {
return
@@ -87,7 +86,7 @@
if (DeviceEntryUdfpsRefactor.isEnabled) {
DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId }
} else {
- // keyguardBottomAreaRefactor() or migrateClocksToBlueprint()
+ // KeyguardBottomAreaRefactor.isEnabled or MigrateClocksToBlueprint.isEnabled
LockIconView(context, null).apply { id = R.id.lock_icon_view }
}
constraintLayout.addView(view)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
index 3361343..af0528a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -21,7 +21,7 @@
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
@@ -42,14 +42,14 @@
private var indicationAreaHandle: DisposableHandle? = null
override fun addViews(constraintLayout: ConstraintLayout) {
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
val view = KeyguardIndicationArea(context, null)
constraintLayout.addView(view)
}
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
indicationAreaHandle =
KeyguardIndicationAreaBinder.bind(
constraintLayout.requireViewById(R.id.keyguard_indication_area),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index c1b0cc6..380e361 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -25,13 +25,11 @@
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.Flags.centralizedStatusBarHeightFix
-import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
-import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackViewBinder
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import dagger.Lazy
@@ -42,31 +40,27 @@
@Inject
constructor(
context: Context,
- sceneContainerFlags: SceneContainerFlags,
notificationPanelView: NotificationPanelView,
sharedNotificationContainer: SharedNotificationContainer,
sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
sharedNotificationContainerBinder: SharedNotificationContainerBinder,
- notificationStackViewBinder: NotificationStackViewBinder,
private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
) :
NotificationStackScrollLayoutSection(
context,
- sceneContainerFlags,
notificationPanelView,
sharedNotificationContainer,
sharedNotificationContainerViewModel,
sharedNotificationContainerBinder,
- notificationStackViewBinder,
) {
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
constraintSet.apply {
val bottomMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
val useLargeScreenHeader =
context.resources.getBoolean(R.bool.config_use_large_screen_shade_header)
val marginTopLargeScreen =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
index a203c53..32e76d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
@@ -29,9 +29,9 @@
import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import androidx.core.view.isVisible
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
@@ -56,7 +56,7 @@
private var settingsPopupMenuHandle: DisposableHandle? = null
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!keyguardBottomAreaRefactor()) {
+ if (!KeyguardBottomAreaRefactor.isEnabled) {
return
}
val view =
@@ -71,7 +71,7 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
settingsPopupMenuHandle =
KeyguardSettingsViewBinder.bind(
constraintLayout.requireViewById<View>(R.id.keyguard_settings_button),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 0c0eb8a..45b8257 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -25,8 +25,8 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -48,14 +48,14 @@
private val vibratorHelper: VibratorHelper,
) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
addLeftShortcut(constraintLayout)
addRightShortcut(constraintLayout)
}
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
leftShortcutHandle =
KeyguardQuickAffordanceViewBinder.bind(
constraintLayout.requireViewById(R.id.start_button),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
index 6e8605b..45641db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
@@ -31,8 +31,8 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.keyguard.KeyguardStatusView
import com.android.keyguard.dagger.KeyguardStatusViewComponent
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.KeyguardViewConfigurator
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
import com.android.systemui.res.R
@@ -58,7 +58,7 @@
private val statusViewId = R.id.keyguard_status_view
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
// At startup, 2 views with the ID `R.id.keyguard_status_view` will be available.
@@ -83,7 +83,7 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
constraintLayout.findViewById<KeyguardStatusView?>(R.id.keyguard_status_view)?.let {
val statusViewComponent =
keyguardStatusViewComponentFactory.build(it, context.display)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
index 3265d79..2abb7ba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
@@ -20,10 +20,10 @@
import android.content.Context
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.Flags
import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder
import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
import com.android.systemui.deviceentry.ui.viewmodel.DeviceEntryUdfpsAccessibilityOverlayViewModel
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
import javax.inject.Inject
@@ -66,7 +66,7 @@
ConstraintSet.BOTTOM,
)
- if (Flags.keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
connect(
viewId,
ConstraintSet.BOTTOM,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt
index d572c51..a17c5e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt
@@ -22,7 +22,7 @@
import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
@@ -34,7 +34,7 @@
val smartspaceController: LockscreenSmartspaceController,
) : KeyguardSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) return
+ if (!MigrateClocksToBlueprint.isEnabled) return
if (smartspaceController.isEnabled()) return
constraintLayout.findViewById<View?>(R.id.keyguard_slice_view)?.let {
@@ -46,7 +46,7 @@
override fun bindData(constraintLayout: ConstraintLayout) {}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!migrateClocksToBlueprint()) return
+ if (!MigrateClocksToBlueprint.isEnabled) return
if (smartspaceController.isEnabled()) return
constraintSet.apply {
@@ -81,7 +81,7 @@
}
override fun removeViews(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) return
+ if (!MigrateClocksToBlueprint.isEnabled) return
if (smartspaceController.isEnabled()) return
constraintLayout.removeView(R.id.keyguard_slice_view)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index 8323502..2b601cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -25,30 +25,26 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
-import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackViewBinder
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
-import com.android.systemui.util.kotlin.DisposableHandles
+import kotlinx.coroutines.DisposableHandle
abstract class NotificationStackScrollLayoutSection
constructor(
protected val context: Context,
- private val sceneContainerFlags: SceneContainerFlags,
private val notificationPanelView: NotificationPanelView,
private val sharedNotificationContainer: SharedNotificationContainer,
private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
private val sharedNotificationContainerBinder: SharedNotificationContainerBinder,
- private val notificationStackViewBinder: NotificationStackViewBinder,
) : KeyguardSection() {
private val placeHolderId = R.id.nssl_placeholder
- private val disposableHandles = DisposableHandles()
+ private var disposableHandle: DisposableHandle? = null
/**
* Align the notification placeholder bottom to the top of either the lock icon or the ambient
@@ -74,7 +70,7 @@
}
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
// This moves the existing NSSL view to a different parent, as the controller is a
@@ -90,24 +86,21 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
- disposableHandles.dispose()
- disposableHandles +=
+ disposableHandle?.dispose()
+ disposableHandle =
sharedNotificationContainerBinder.bind(
sharedNotificationContainer,
sharedNotificationContainerViewModel,
)
-
- if (sceneContainerFlags.isEnabled()) {
- disposableHandles += notificationStackViewBinder.bindWhileAttached()
- }
}
override fun removeViews(constraintLayout: ConstraintLayout) {
- disposableHandles.dispose()
+ disposableHandle?.dispose()
+ disposableHandle = null
constraintLayout.removeView(placeHolderId)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index b0f7a25..1847d27 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -23,8 +23,8 @@
import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
@@ -56,7 +56,7 @@
private var pastVisibility: Int = -1
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) return
+ if (!MigrateClocksToBlueprint.isEnabled) return
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
smartspaceView = smartspaceController.buildAndConnectView(constraintLayout)
weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout)
@@ -83,7 +83,7 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) return
+ if (!MigrateClocksToBlueprint.isEnabled) return
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
KeyguardSmartspaceViewBinder.bind(
constraintLayout,
@@ -94,7 +94,7 @@
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!migrateClocksToBlueprint()) return
+ if (!MigrateClocksToBlueprint.isEnabled) return
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
val horizontalPaddingStart =
context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
@@ -191,7 +191,7 @@
}
override fun removeViews(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) return
+ if (!MigrateClocksToBlueprint.isEnabled) return
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
listOf(smartspaceView, dateView, weatherView).forEach {
it?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
index 21e9455..5dbba75 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -28,7 +28,7 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
import com.android.systemui.res.R
@@ -46,7 +46,7 @@
private val mediaContainerId = R.id.status_view_media_container
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
@@ -73,7 +73,7 @@
override fun bindData(constraintLayout: ConstraintLayout) {}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
@@ -87,7 +87,7 @@
}
override fun removeViews(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index 4a705a7..1a73866 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -23,12 +23,10 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
-import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackViewBinder
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import javax.inject.Inject
@@ -38,24 +36,20 @@
@Inject
constructor(
context: Context,
- sceneContainerFlags: SceneContainerFlags,
notificationPanelView: NotificationPanelView,
sharedNotificationContainer: SharedNotificationContainer,
sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
sharedNotificationContainerBinder: SharedNotificationContainerBinder,
- notificationStackViewBinder: NotificationStackViewBinder,
) :
NotificationStackScrollLayoutSection(
context,
- sceneContainerFlags,
notificationPanelView,
sharedNotificationContainer,
sharedNotificationContainerViewModel,
sharedNotificationContainerBinder,
- notificationStackViewBinder,
) {
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
return
}
constraintSet.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
index 5741b94..1e5f5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
@@ -18,8 +18,8 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
@@ -60,7 +60,7 @@
emit(goneToAodAlpha)
} else if (step.from == GONE && step.to == DOZING) {
emit(goneToDozingAlpha)
- } else if (!migrateClocksToBlueprint()) {
+ } else if (!MigrateClocksToBlueprint.isEnabled) {
emit(keyguardAlpha)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 9c1f077..2054932 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -22,9 +22,9 @@
import android.util.MathUtils
import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardClockSwitch
-import com.android.systemui.Flags
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -145,7 +145,7 @@
// Ensure the desired translation doesn't encroach on the top inset
val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt()
val translationY =
- if (Flags.migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
max(params.topInset - params.minViewY, burnInY)
} else {
max(params.topInset, params.minViewY + burnInY) - params.minViewY
@@ -168,7 +168,7 @@
private fun clockController(
provider: Provider<ClockController>?,
): Provider<ClockController>? {
- return if (Flags.migrateClocksToBlueprint()) {
+ return if (MigrateClocksToBlueprint.isEnabled) {
Provider { keyguardClockViewModel.currentClock.value }
} else {
provider
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
index e0b1c50..a2ce408 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
@@ -43,9 +43,11 @@
transitionAnimation.sharedFlow(
duration = 250.milliseconds,
onStep = { it },
- onCancel = { 0f },
+ onCancel = { 1f },
)
+ val lockscreenAlpha: Flow<Float> = shortcutsAlpha
+
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(1f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index e35e065..8409f15 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -16,10 +16,10 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -52,7 +52,7 @@
/** An observable for whether the indication area should be padded. */
val isIndicationAreaPadded: Flow<Boolean> =
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled) {
combine(shortcutsCombinedViewModel.startButton, shortcutsCombinedViewModel.endButton) {
startButtonModel,
endButtonModel ->
@@ -79,7 +79,7 @@
/** An observable for the x-offset by which the indication area should be translated. */
val indicationAreaTranslationX: Flow<Float> =
- if (migrateClocksToBlueprint() || keyguardBottomAreaRefactor()) {
+ if (MigrateClocksToBlueprint.isEnabled || KeyguardBottomAreaRefactor.isEnabled) {
burnIn.map { it.translationX.toFloat() }
} else {
bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
@@ -87,7 +87,7 @@
/** Returns an observable for the y-offset by which the indication area should be translated. */
fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
- return if (migrateClocksToBlueprint()) {
+ return if (MigrateClocksToBlueprint.isEnabled) {
burnIn.map { it.translationY.toFloat() }
} else {
keyguardInteractor.dozeAmount
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index b662109..5337ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -91,6 +91,7 @@
private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
private val goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel,
private val goneToDreamingTransitionViewModel: GoneToDreamingTransitionViewModel,
+ private val goneToLockscreenTransitionViewModel: GoneToLockscreenTransitionViewModel,
private val lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
private val lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
private val lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel,
@@ -218,6 +219,7 @@
goneToAodTransitionViewModel.enterFromTopAnimationAlpha,
goneToDozingTransitionViewModel.lockscreenAlpha,
goneToDreamingTransitionViewModel.lockscreenAlpha,
+ goneToLockscreenTransitionViewModel.lockscreenAlpha,
lockscreenToAodTransitionViewModel.lockscreenAlpha(viewState),
lockscreenToDozingTransitionViewModel.lockscreenAlpha,
lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
index ba7d410..89a9ba7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
@@ -27,10 +27,10 @@
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule.KEYGUARD
@@ -185,7 +185,7 @@
refreshMediaPosition(reason = "onMediaHostVisibilityChanged")
if (visible) {
- if (migrateClocksToBlueprint() && useSplitShade) {
+ if (MigrateClocksToBlueprint.isEnabled && useSplitShade) {
return
}
mediaHost.hostView.layoutParams.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 768bb8e..4fe3a11 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -934,48 +934,51 @@
private void orientSecondaryHomeHandle() {
if (!canShowSecondaryHandle()) {
- if (mStartingQuickSwitchRotation == -1) {
- resetSecondaryHandle();
- }
return;
}
- int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation);
- if (mStartingQuickSwitchRotation == -1 || deltaRotation == -1) {
- // Curious if starting quickswitch can change between the if check and our delta
- Log.d(TAG, "secondary nav delta rotation: " + deltaRotation
- + " current: " + mCurrentRotation
- + " starting: " + mStartingQuickSwitchRotation);
- }
- int height = 0;
- int width = 0;
- Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds();
- mOrientationHandle.setDeltaRotation(deltaRotation);
- switch (deltaRotation) {
- case Surface.ROTATION_90, Surface.ROTATION_270:
- height = dispSize.height();
- width = mView.getHeight();
- break;
- case Surface.ROTATION_180, Surface.ROTATION_0:
- // TODO(b/152683657): Need to determine best UX for this
- if (!mShowOrientedHandleForImmersiveMode) {
- resetSecondaryHandle();
- return;
- }
- width = dispSize.width();
- height = mView.getHeight();
- break;
- }
+ if (mStartingQuickSwitchRotation == -1) {
+ resetSecondaryHandle();
+ } else {
+ int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation);
+ if (mStartingQuickSwitchRotation == -1 || deltaRotation == -1) {
+ // Curious if starting quickswitch can change between the if check and our delta
+ Log.d(TAG, "secondary nav delta rotation: " + deltaRotation
+ + " current: " + mCurrentRotation
+ + " starting: " + mStartingQuickSwitchRotation);
+ }
+ int height = 0;
+ int width = 0;
+ Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds();
+ mOrientationHandle.setDeltaRotation(deltaRotation);
+ switch (deltaRotation) {
+ case Surface.ROTATION_90:
+ case Surface.ROTATION_270:
+ height = dispSize.height();
+ width = mView.getHeight();
+ break;
+ case Surface.ROTATION_180:
+ case Surface.ROTATION_0:
+ // TODO(b/152683657): Need to determine best UX for this
+ if (!mShowOrientedHandleForImmersiveMode) {
+ resetSecondaryHandle();
+ return;
+ }
+ width = dispSize.width();
+ height = mView.getHeight();
+ break;
+ }
- mOrientationParams.gravity =
- deltaRotation == Surface.ROTATION_0 ? Gravity.BOTTOM :
- (deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT);
- mOrientationParams.height = height;
- mOrientationParams.width = width;
- mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams);
- mView.setVisibility(View.GONE);
- mOrientationHandle.setVisibility(View.VISIBLE);
- logNavbarOrientation("orientSecondaryHomeHandle");
+ mOrientationParams.gravity =
+ deltaRotation == Surface.ROTATION_0 ? Gravity.BOTTOM :
+ (deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT);
+ mOrientationParams.height = height;
+ mOrientationParams.width = width;
+ mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams);
+ mView.setVisibility(View.GONE);
+ mOrientationHandle.setVisibility(View.VISIBLE);
+ logNavbarOrientation("orientSecondaryHomeHandle");
+ }
}
private void resetSecondaryHandle() {
@@ -1789,8 +1792,7 @@
}
private boolean canShowSecondaryHandle() {
- return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null
- && mStartingQuickSwitchRotation != -1;
+ return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null;
}
private final UserTracker.Callback mUserChangedCallback =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
index fc42ba4..b25c61c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
@@ -39,7 +39,7 @@
return sysuiDialogFactory.create(this, context)
}
- override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
with(dialog) {
setTitle(R.string.data_saver_enable_title)
setMessage(R.string.data_saver_description)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileDataInteractor.kt
new file mode 100644
index 0000000..a2a9e87a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileDataInteractor.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.work.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.work.domain.model.WorkModeTileModel
+import com.android.systemui.statusbar.phone.ManagedProfileController
+import com.android.systemui.util.kotlin.hasActiveWorkProfile
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Observes data saver state changes providing the [WorkModeTileModel]. */
+class WorkModeTileDataInteractor
+@Inject
+constructor(
+ private val profileController: ManagedProfileController,
+) : QSTileDataInteractor<WorkModeTileModel> {
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<WorkModeTileModel> =
+ profileController.hasActiveWorkProfile.map { hasActiveWorkProfile: Boolean ->
+ if (hasActiveWorkProfile) {
+ WorkModeTileModel.HasActiveProfile(profileController.isWorkModeEnabled)
+ } else {
+ WorkModeTileModel.NoActiveProfile
+ }
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> =
+ profileController.hasActiveWorkProfile
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractor.kt
new file mode 100644
index 0000000..f765f8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractor.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.work.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.work.domain.model.WorkModeTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.phone.ManagedProfileController
+import javax.inject.Inject
+
+/** Handles airplane mode tile clicks and long clicks. */
+class WorkModeTileUserActionInteractor
+@Inject
+constructor(
+ private val profileController: ManagedProfileController,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<WorkModeTileModel> {
+ override suspend fun handleInput(input: QSTileInput<WorkModeTileModel>) =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ if (data is WorkModeTileModel.HasActiveProfile) {
+ profileController.setWorkModeEnabled(!data.isEnabled)
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ if (data is WorkModeTileModel.HasActiveProfile) {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_MANAGED_PROFILE_SETTINGS)
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/model/WorkModeTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/model/WorkModeTileModel.kt
new file mode 100644
index 0000000..ae8382d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/model/WorkModeTileModel.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.work.domain.model
+
+/** Work mode tile model. */
+sealed interface WorkModeTileModel {
+ /** @param isEnabled is true when the work mode is enabled */
+ data class HasActiveProfile(val isEnabled: Boolean) : WorkModeTileModel
+ data object NoActiveProfile : WorkModeTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
new file mode 100644
index 0000000..55445bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.work.ui
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_WORK_PROFILE_LABEL
+import android.content.res.Resources
+import android.service.quicksettings.Tile
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.work.domain.model.WorkModeTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [WorkModeTileModel] to [QSTileState]. */
+class WorkModeTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+ private val devicePolicyManager: DevicePolicyManager,
+) : QSTileDataToStateMapper<WorkModeTileModel> {
+ override fun map(config: QSTileConfig, data: WorkModeTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = getTileLabel()!!
+ contentDescription = label
+
+ icon = {
+ Icon.Loaded(
+ resources.getDrawable(
+ com.android.internal.R.drawable.stat_sys_managed_profile_status,
+ theme
+ ),
+ contentDescription = null
+ )
+ }
+
+ when (data) {
+ is WorkModeTileModel.HasActiveProfile -> {
+ if (data.isEnabled) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ secondaryLabel = ""
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ secondaryLabel =
+ resources.getString(R.string.quick_settings_work_mode_paused_state)
+ }
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+ is WorkModeTileModel.NoActiveProfile -> {
+ activationState = QSTileState.ActivationState.UNAVAILABLE
+ secondaryLabel =
+ resources.getStringArray(R.array.tile_states_work)[Tile.STATE_UNAVAILABLE]
+ supportedActions = setOf()
+ }
+ }
+
+ sideViewIcon = QSTileState.SideViewIcon.None
+ }
+
+ private fun getTileLabel(): CharSequence? {
+ return devicePolicyManager.resources.getString(QS_WORK_PROFILE_LABEL) {
+ resources.getString(R.string.quick_settings_work_mode_label)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 467089d..54ec398 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -18,18 +18,15 @@
package com.android.systemui.scene.shared.flag
-import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
-import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
-import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.Flags.keyguardWmStateRefactor
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.Flags.sceneContainer
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.Flags.SCENE_CONTAINER_ENABLED
import com.android.systemui.flags.RefactorFlagUtils
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
import dagger.Module
@@ -45,11 +42,11 @@
get() =
SCENE_CONTAINER_ENABLED && // mainStaticFlag
sceneContainer() && // mainAconfigFlag
- keyguardBottomAreaRefactor() &&
- migrateClocksToBlueprint() &&
+ KeyguardBottomAreaRefactor.isEnabled &&
+ MigrateClocksToBlueprint.isEnabled &&
ComposeLockscreen.isEnabled &&
MediaInSceneContainerFlag.isEnabled &&
- keyguardWmStateRefactor()
+ KeyguardWmStateRefactor.isEnabled
// NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
/**
@@ -66,9 +63,9 @@
/** The set of secondary flags which must be enabled for scene container to work properly */
inline fun getSecondaryFlags(): Sequence<FlagToken> =
sequenceOf(
- FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
- FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint()),
- FlagToken(FLAG_KEYGUARD_WM_STATE_REFACTOR, keyguardWmStateRefactor()),
+ KeyguardBottomAreaRefactor.token,
+ MigrateClocksToBlueprint.token,
+ KeyguardWmStateRefactor.token,
ComposeLockscreen.token,
MediaInSceneContainerFlag.token,
// NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 9cb920a..2de14dd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -24,8 +24,6 @@
import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
-import static com.android.systemui.Flags.keyguardBottomAreaRefactor;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
import static com.android.systemui.Flags.predictiveBackAnimateShade;
import static com.android.systemui.Flags.smartspaceRelocateToBottom;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
@@ -129,8 +127,10 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.keyguard.KeyguardBottomAreaRefactor;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewConfigurator;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -1018,7 +1018,7 @@
instantCollapse();
} else {
mView.animate().cancel();
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mView.animate()
.alpha(0f)
.setStartDelay(0)
@@ -1075,7 +1075,7 @@
mQsController.init();
mShadeHeadsUpTracker.addTrackingHeadsUpListener(
mNotificationStackScrollLayoutController::setTrackingHeadsUp);
- if (!keyguardBottomAreaRefactor()) {
+ if (!KeyguardBottomAreaRefactor.isEnabled()) {
setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
}
@@ -1154,7 +1154,7 @@
// Occluded->Lockscreen
collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(),
mOccludedToLockscreenTransition, mMainDispatcher);
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(),
setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
collectFlow(mView,
@@ -1165,7 +1165,7 @@
// Lockscreen->Dreaming
collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(),
mLockscreenToDreamingTransition, mMainDispatcher);
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
mMainDispatcher);
@@ -1177,7 +1177,7 @@
// Gone->Dreaming
collectFlow(mView, mKeyguardTransitionInteractor.getGoneToDreamingTransition(),
mGoneToDreamingTransition, mMainDispatcher);
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(),
setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
}
@@ -1188,7 +1188,7 @@
// Lockscreen->Occluded
collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(),
mLockscreenToOccludedTransition, mMainDispatcher);
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(),
setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenTranslationY(),
@@ -1196,7 +1196,7 @@
}
// Primary bouncer->Gone (ensures lockscreen content is not visible on successful auth)
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha(),
setTransitionAlpha(mNotificationStackScrollLayoutController,
/* excludeNotifications=*/ true), mMainDispatcher);
@@ -1280,7 +1280,7 @@
mKeyguardStatusViewController.onDestroy();
}
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
// Need a shared controller until mKeyguardStatusViewController can be removed from
// here, due to important state being set in that controller. Rebind in order to pick
// up config changes
@@ -1332,13 +1332,13 @@
private void onSplitShadeEnabledChanged() {
mShadeLog.logSplitShadeChanged(mSplitShadeEnabled);
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
}
// Reset any left over overscroll state. It is a rare corner case but can happen.
mQsController.setOverScrollAmount(0);
mScrimController.setNotificationsOverScrollAmount(0);
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mNotificationStackScrollLayoutController.setOverExpansion(0);
mNotificationStackScrollLayoutController.setOverScrollAmount(0);
}
@@ -1359,7 +1359,7 @@
}
updateClockAppearance();
mQsController.updateQsState();
- if (!migrateClocksToBlueprint() && !FooterViewRefactor.isEnabled()) {
+ if (!MigrateClocksToBlueprint.isEnabled() && !FooterViewRefactor.isEnabled()) {
mNotificationStackScrollLayoutController.updateFooter();
}
}
@@ -1391,7 +1391,7 @@
void reInflateViews() {
debugLog("reInflateViews");
// Re-inflate the status view group.
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
KeyguardStatusView keyguardStatusView =
mNotificationContainerParent.findViewById(R.id.keyguard_status_view);
int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView);
@@ -1430,7 +1430,7 @@
updateViewControllers(userAvatarView, keyguardUserSwitcherView);
- if (!keyguardBottomAreaRefactor()) {
+ if (!KeyguardBottomAreaRefactor.isEnabled()) {
// Update keyguard bottom area
int index = mView.indexOfChild(mKeyguardBottomArea);
mView.removeView(mKeyguardBottomArea);
@@ -1449,7 +1449,7 @@
mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
mStatusBarStateController.getInterpolatedDozeAmount());
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
mBarState,
false,
@@ -1471,7 +1471,7 @@
mBarState);
}
- if (!keyguardBottomAreaRefactor()) {
+ if (!KeyguardBottomAreaRefactor.isEnabled()) {
setKeyguardBottomAreaVisibility(mBarState, false);
}
@@ -1480,14 +1480,14 @@
}
private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
mKeyguardMediaController.attachSplitShadeContainer(container);
}
private void initBottomArea() {
- if (!keyguardBottomAreaRefactor()) {
+ if (!KeyguardBottomAreaRefactor.isEnabled()) {
mKeyguardBottomArea.init(
mKeyguardBottomAreaViewModel,
mFalsingManager,
@@ -1513,7 +1513,7 @@
}
private void updateMaxDisplayedNotifications(boolean recompute) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
@@ -1630,7 +1630,7 @@
int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard;
boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange();
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
mKeyguardClockInteractor.setClockSize(computeDesiredClockSize());
} else {
mKeyguardStatusViewController.displayClock(computeDesiredClockSize(),
@@ -1671,11 +1671,11 @@
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mKeyguardStatusViewController.setLockscreenClockY(
mClockPositionAlgorithm.getExpandedPreferredClockY());
}
- if (!(migrateClocksToBlueprint() || keyguardBottomAreaRefactor())) {
+ if (!(MigrateClocksToBlueprint.isEnabled() || KeyguardBottomAreaRefactor.isEnabled())) {
mKeyguardBottomAreaInteractor.setClockPosition(
mClockPositionResult.clockX, mClockPositionResult.clockY);
}
@@ -1683,7 +1683,7 @@
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mKeyguardStatusViewController.updatePosition(
mClockPositionResult.clockX, mClockPositionResult.clockY,
mClockPositionResult.clockScale, animateClock);
@@ -1740,7 +1740,7 @@
// To prevent the weather clock from overlapping with the notification shelf on AOD, we use
// the small clock here
// With migrateClocksToBlueprint, weather clock will have behaviors similar to other clocks
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf()
&& hasVisibleNotifications() && isOnAod()) {
return SMALL;
@@ -1758,7 +1758,7 @@
private void updateKeyguardStatusViewAlignment(boolean animate) {
boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
return;
}
@@ -1941,7 +1941,7 @@
}
float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
mKeyguardStatusViewController.setAlpha(alpha);
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
// TODO (b/296373478) This is for split shade media movement.
} else {
mKeyguardStatusViewController
@@ -2498,7 +2498,7 @@
}
if (!mKeyguardBypassController.getBypassEnabled()) {
- if (migrateClocksToBlueprint() && !mSplitShadeEnabled) {
+ if (MigrateClocksToBlueprint.isEnabled() && !mSplitShadeEnabled) {
return (int) mKeyguardInteractor.getNotificationContainerBounds()
.getValue().getTop();
}
@@ -2531,7 +2531,7 @@
void requestScrollerTopPaddingUpdate(boolean animate) {
float padding = mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing,
getKeyguardNotificationStaticPadding(), mExpandedFraction);
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
mSharedNotificationContainerInteractor.setTopPosition(padding);
} else {
mNotificationStackScrollLayoutController.updateTopPadding(padding, animate);
@@ -2712,7 +2712,7 @@
return;
}
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
float alpha = 1f;
if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp
&& !mHeadsUpManager.hasPinnedHeadsUp()) {
@@ -2748,7 +2748,7 @@
}
private void updateKeyguardBottomAreaAlpha() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
if (mIsOcclusionTransitionRunning) {
@@ -2766,7 +2766,7 @@
float alpha = Math.min(expansionAlpha, 1 - mQsController.computeExpansionFraction());
alpha *= mBottomAreaShadeAlpha;
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled()) {
mKeyguardInteractor.setAlpha(alpha);
} else {
mKeyguardBottomAreaInteractor.setAlpha(alpha);
@@ -2978,7 +2978,7 @@
}
private void updateDozingVisibilities(boolean animate) {
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled()) {
mKeyguardInteractor.setAnimateDozingTransitions(animate);
} else {
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
@@ -2990,7 +2990,7 @@
@Override
public void onScreenTurningOn() {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mKeyguardStatusViewController.dozeTimeTick();
}
}
@@ -3189,7 +3189,7 @@
mDozing = dozing;
// TODO (b/) make listeners for this
mNotificationStackScrollLayoutController.setDozing(mDozing, animate);
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled()) {
mKeyguardInteractor.setAnimateDozingTransitions(animate);
} else {
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
@@ -3245,7 +3245,7 @@
public void dozeTimeTick() {
mLockIconViewController.dozeTimeTick();
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mKeyguardStatusViewController.dozeTimeTick();
}
if (mInterpolatedDarkAmount > 0) {
@@ -3324,7 +3324,7 @@
/** Updates the views to the initial state for the fold to AOD animation. */
@Override
public void prepareFoldToAodAnimation() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
// Force show AOD UI even if we are not locked
@@ -3348,7 +3348,7 @@
@Override
public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
Runnable cancelAction) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
final ViewPropertyAnimator viewAnimator = mView.animate();
@@ -3386,7 +3386,7 @@
/** Cancels fold to AOD transition and resets view state. */
@Override
public void cancelFoldToAodAnimation() {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return;
}
cancelAnimation();
@@ -4460,7 +4460,7 @@
&& statusBarState == KEYGUARD) {
// This means we're doing the screen off animation - position the keyguard status
// view where it'll be on AOD, so we can animate it in.
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mKeyguardStatusViewController.updatePosition(
mClockPositionResult.clockX,
mClockPositionResult.clockYFullyDozing,
@@ -4469,7 +4469,7 @@
}
}
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
statusBarState,
keyguardFadingAway,
@@ -4477,7 +4477,7 @@
mBarState);
}
- if (!keyguardBottomAreaRefactor()) {
+ if (!KeyguardBottomAreaRefactor.isEnabled()) {
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
}
@@ -4582,7 +4582,7 @@
setDozing(true /* dozing */, false /* animate */);
mStatusBarStateController.setUpcomingState(KEYGUARD);
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
mStatusBarStateController.setState(KEYGUARD);
} else {
mStatusBarStateListener.onStateChanged(KEYGUARD);
@@ -4645,7 +4645,7 @@
setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
// Update Clock Pivot (used by anti-burnin transformations)
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight());
}
@@ -4746,7 +4746,7 @@
stackScroller.setMaxAlphaForKeyguard(alpha, "NPVC.setTransitionAlpha()");
}
- if (keyguardBottomAreaRefactor()) {
+ if (KeyguardBottomAreaRefactor.isEnabled()) {
mKeyguardInteractor.setAlpha(alpha);
} else {
mKeyguardBottomAreaInteractor.setAlpha(alpha);
@@ -4765,7 +4765,7 @@
private Consumer<Float> setTransitionY(
NotificationStackScrollLayoutController stackScroller) {
return (Float translationY) -> {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mKeyguardStatusViewController.setTranslationY(translationY,
/* excludeMedia= */false);
stackScroller.setTranslationY(translationY);
@@ -4807,7 +4807,7 @@
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (migrateClocksToBlueprint() && !mUseExternalTouch) {
+ if (MigrateClocksToBlueprint.isEnabled() && !mUseExternalTouch) {
return false;
}
@@ -4878,7 +4878,7 @@
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mCentralSurfaces.userActivity();
}
mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
@@ -4979,7 +4979,7 @@
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (migrateClocksToBlueprint() && !mUseExternalTouch) {
+ if (MigrateClocksToBlueprint.isEnabled() && !mUseExternalTouch) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index e577178..e8e629c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -16,7 +16,6 @@
package com.android.systemui.shade;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -48,6 +47,7 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -320,7 +320,7 @@
mTouchActive = true;
mTouchCancelled = false;
mDownEvent = ev;
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
mService.userActivity();
}
} else if (ev.getActionMasked() == MotionEvent.ACTION_UP
@@ -475,7 +475,7 @@
&& !bouncerShowing
&& !mStatusBarStateController.isDozing()) {
if (mDragDownHelper.isDragDownEnabled()) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
// When on lockscreen, if the touch originates at the top of the screen
// go directly to QS and not the shade
if (mStatusBarStateController.getState() == KEYGUARD
@@ -488,7 +488,7 @@
// This handles drag down over lockscreen
boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
if (result) {
mLastInterceptWasDragDownHelper = true;
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -511,7 +511,7 @@
return true;
}
}
- } else if (migrateClocksToBlueprint()) {
+ } else if (MigrateClocksToBlueprint.isEnabled()) {
// This final check handles swipes on HUNs and when Pulsing
if (!bouncerShowing && didNotificationPanelInterceptEvent(ev)) {
mShadeLogger.d("NSWVC: intercepted for HUN/PULSING");
@@ -526,7 +526,7 @@
MotionEvent cancellation = MotionEvent.obtain(ev);
cancellation.setAction(MotionEvent.ACTION_CANCEL);
mStackScrollLayout.onInterceptTouchEvent(cancellation);
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
}
cancellation.recycle();
@@ -541,7 +541,7 @@
if (mStatusBarKeyguardViewManager.onTouch(ev)) {
return true;
}
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
// we still want to finish our drag down gesture when locking the screen
handled |= mDragDownHelper.onTouchEvent(ev) || handled;
@@ -631,7 +631,7 @@
}
private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
// Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
// to also ask NotificationPanelViewController directly, in order to process swipe up
// events originating from notifications
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 29de688..8b88da1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -28,10 +28,10 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.lifecycle.lifecycleScope
import com.android.systemui.Flags.centralizedStatusBarHeightFix
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.fragments.FragmentService
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.plugins.qs.QS
@@ -52,11 +52,12 @@
import kotlin.reflect.KMutableProperty0
import kotlinx.coroutines.launch
-@VisibleForTesting
-internal const val INSET_DEBOUNCE_MILLIS = 500L
+@VisibleForTesting internal const val INSET_DEBOUNCE_MILLIS = 500L
@SysUISingleton
-class NotificationsQSContainerController @Inject constructor(
+class NotificationsQSContainerController
+@Inject
+constructor(
view: NotificationsQuickSettingsContainer,
private val navigationModeController: NavigationModeController,
private val overviewProxyService: OverviewProxyService,
@@ -64,8 +65,7 @@
private val shadeInteractor: ShadeInteractor,
private val fragmentService: FragmentService,
@Main private val delayableExecutor: DelayableExecutor,
- private val
- notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+ private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
private val splitShadeStateController: SplitShadeStateController,
private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
@@ -88,45 +88,48 @@
private var isGestureNavigation = true
private var taskbarVisible = false
- private val taskbarVisibilityListener: OverviewProxyListener = object : OverviewProxyListener {
- override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
- taskbarVisible = visible
+ private val taskbarVisibilityListener: OverviewProxyListener =
+ object : OverviewProxyListener {
+ override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
+ taskbarVisible = visible
+ }
}
- }
// With certain configuration changes (like light/dark changes), the nav bar will disappear
// for a bit, causing `bottomStableInsets` to be unstable for some time. Debounce the value
// for 500ms.
// All interactions with this object happen in the main thread.
- private val delayedInsetSetter = object : Runnable, Consumer<WindowInsets> {
- private var canceller: Runnable? = null
- private var stableInsets = 0
- private var cutoutInsets = 0
+ private val delayedInsetSetter =
+ object : Runnable, Consumer<WindowInsets> {
+ private var canceller: Runnable? = null
+ private var stableInsets = 0
+ private var cutoutInsets = 0
- override fun accept(insets: WindowInsets) {
- // when taskbar is visible, stableInsetBottom will include its height
- stableInsets = insets.stableInsetBottom
- cutoutInsets = insets.displayCutout?.safeInsetBottom ?: 0
- canceller?.run()
- canceller = delayableExecutor.executeDelayed(this, INSET_DEBOUNCE_MILLIS)
- }
+ override fun accept(insets: WindowInsets) {
+ // when taskbar is visible, stableInsetBottom will include its height
+ stableInsets = insets.stableInsetBottom
+ cutoutInsets = insets.displayCutout?.safeInsetBottom ?: 0
+ canceller?.run()
+ canceller = delayableExecutor.executeDelayed(this, INSET_DEBOUNCE_MILLIS)
+ }
- override fun run() {
- bottomStableInsets = stableInsets
- bottomCutoutInsets = cutoutInsets
- updateBottomSpacing()
+ override fun run() {
+ bottomStableInsets = stableInsets
+ bottomCutoutInsets = cutoutInsets
+ updateBottomSpacing()
+ }
}
- }
override fun onInit() {
mView.repeatWhenAttached {
lifecycleScope.launch {
- shadeInteractor.isQsExpanded.collect{ _ -> mView.invalidate() }
+ shadeInteractor.isQsExpanded.collect { _ -> mView.invalidate() }
}
}
- val currentMode: Int = navigationModeController.addListener { mode: Int ->
- isGestureNavigation = QuickStepContract.isGesturalMode(mode)
- }
+ val currentMode: Int =
+ navigationModeController.addListener { mode: Int ->
+ isGestureNavigation = QuickStepContract.isGesturalMode(mode)
+ }
isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
mView.setStackScroller(notificationStackScrollLayoutController.getView())
@@ -151,30 +154,35 @@
fun updateResources() {
val newSplitShadeEnabled =
- splitShadeStateController.shouldUseSplitNotificationShade(resources)
+ splitShadeStateController.shouldUseSplitNotificationShade(resources)
val splitShadeEnabledChanged = newSplitShadeEnabled != splitShadeEnabled
splitShadeEnabled = newSplitShadeEnabled
largeScreenShadeHeaderActive = LargeScreenUtils.shouldUseLargeScreenShadeHeader(resources)
- notificationsBottomMargin = resources.getDimensionPixelSize(
- R.dimen.notification_panel_margin_bottom)
+ notificationsBottomMargin =
+ resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
largeScreenShadeHeaderHeight = calculateLargeShadeHeaderHeight()
shadeHeaderHeight = calculateShadeHeaderHeight()
- panelMarginHorizontal = resources.getDimensionPixelSize(
- R.dimen.notification_panel_margin_horizontal)
- topMargin = if (largeScreenShadeHeaderActive) {
- largeScreenShadeHeaderHeight
- } else {
- resources.getDimensionPixelSize(R.dimen.notification_panel_margin_top)
- }
+ panelMarginHorizontal =
+ resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+ topMargin =
+ if (largeScreenShadeHeaderActive) {
+ largeScreenShadeHeaderHeight
+ } else {
+ resources.getDimensionPixelSize(R.dimen.notification_panel_margin_top)
+ }
updateConstraints()
- val scrimMarginChanged = ::scrimShadeBottomMargin.setAndReportChange(
- resources.getDimensionPixelSize(R.dimen.split_shade_notifications_scrim_margin_bottom)
- )
- val footerOffsetChanged = ::footerActionsOffset.setAndReportChange(
- resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset) +
- resources.getDimensionPixelSize(R.dimen.qs_footer_actions_bottom_padding)
- )
+ val scrimMarginChanged =
+ ::scrimShadeBottomMargin.setAndReportChange(
+ resources.getDimensionPixelSize(
+ R.dimen.split_shade_notifications_scrim_margin_bottom
+ )
+ )
+ val footerOffsetChanged =
+ ::footerActionsOffset.setAndReportChange(
+ resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset) +
+ resources.getDimensionPixelSize(R.dimen.qs_footer_actions_bottom_padding)
+ )
val dimensChanged = scrimMarginChanged || footerOffsetChanged
if (splitShadeEnabledChanged || dimensChanged) {
@@ -198,7 +206,7 @@
// 2. carrier_group height (R.dimen.large_screen_shade_header_min_height)
// 3. date height (R.dimen.new_qs_header_non_clickable_element_height)
val estimatedHeight =
- 2 * resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height) +
+ 2 * resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height) +
resources.getDimensionPixelSize(R.dimen.new_qs_header_non_clickable_element_height)
return estimatedHeight.coerceAtLeast(minHeight)
}
@@ -250,16 +258,17 @@
containerPadding = 0
stackScrollMargin = bottomStableInsets + notificationsBottomMargin
}
- val qsContainerPadding = if (!isQSDetailShowing) {
- // We also want this padding in the bottom in these cases
- if (splitShadeEnabled) {
- stackScrollMargin - scrimShadeBottomMargin - footerActionsOffset
+ val qsContainerPadding =
+ if (!isQSDetailShowing) {
+ // We also want this padding in the bottom in these cases
+ if (splitShadeEnabled) {
+ stackScrollMargin - scrimShadeBottomMargin - footerActionsOffset
+ } else {
+ bottomStableInsets
+ }
} else {
- bottomStableInsets
+ 0
}
- } else {
- 0
- }
return Paddings(containerPadding, stackScrollMargin, qsContainerPadding)
}
@@ -284,7 +293,7 @@
}
private fun setNotificationsConstraints(constraintSet: ConstraintSet) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled) {
return
}
val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
@@ -309,8 +318,8 @@
}
private fun setKeyguardStatusViewConstraints(constraintSet: ConstraintSet) {
- val statusViewMarginHorizontal = resources.getDimensionPixelSize(
- R.dimen.status_view_margin_horizontal)
+ val statusViewMarginHorizontal =
+ resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
constraintSet.apply {
setMargin(R.id.keyguard_status_view, START, statusViewMarginHorizontal)
setMargin(R.id.keyguard_status_view, END, statusViewMarginHorizontal)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index e82f2d3..1333055 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -18,8 +18,6 @@
import static androidx.constraintlayout.core.widgets.Optimizer.OPTIMIZATION_GRAPH;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
-
import android.app.Fragment;
import android.content.Context;
import android.content.res.Configuration;
@@ -35,6 +33,7 @@
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
@@ -190,7 +189,7 @@
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- if (migrateClocksToBlueprint()) {
+ if (MigrateClocksToBlueprint.isEnabled()) {
return super.drawChild(canvas, child, drawingTime);
}
int layoutIndex = mLayoutDrawingOrder.indexOf(child);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 8dbcead..3a0e167 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -21,7 +21,6 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS;
import static com.android.systemui.shade.NotificationPanelViewController.FLING_COLLAPSE;
@@ -71,6 +70,7 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
@@ -1778,7 +1778,7 @@
// Dragging down on the lockscreen statusbar should prohibit other interactions
// immediately, otherwise we'll wait on the touchslop. This is to allow
// dragging down to expanded quick settings directly on the lockscreen.
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
}
}
@@ -1823,7 +1823,7 @@
&& Math.abs(h) > Math.abs(x - mInitialTouchX)
&& shouldQuickSettingsIntercept(
mInitialTouchX, mInitialTouchY, h)) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
}
mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index c20efea..6bb1df7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -248,7 +248,7 @@
}
override fun onStatusBarTouch(event: MotionEvent) {
- // The only call to this doesn't happen with migrateClocksToBlueprint() enabled
+ // The only call to this doesn't happen with MigrateClocksToBlueprint.isEnabled enabled
throw UnsupportedOperationException()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 4406813..e7b159a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -529,9 +529,9 @@
default void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {}
/**
- * @see IStatusBar#enterDesktop(int)
+ * @see IStatusBar#moveFocusedTaskToDesktop(int)
*/
- default void enterDesktop(int displayId) {}
+ default void moveFocusedTaskToDesktop(int displayId) {}
}
@VisibleForTesting
@@ -1444,7 +1444,7 @@
}
@Override
- public void enterDesktop(int displayId) {
+ public void moveFocusedTaskToDesktop(int displayId) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = displayId;
mHandler.obtainMessage(MSG_ENTER_DESKTOP, args).sendToTarget();
@@ -1960,7 +1960,7 @@
args = (SomeArgs) msg.obj;
int displayId = args.argi1;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).enterDesktop(displayId);
+ mCallbacks.get(i).moveFocusedTaskToDesktop(displayId);
}
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 4b16126..d974bc44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -15,13 +15,13 @@
import com.android.systemui.Dumpable
import com.android.systemui.ExpandHelper
import com.android.systemui.Flags.nsslFalsingFix
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.Gefingerpoken
import com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
@@ -69,7 +69,7 @@
private val mediaHierarchyManager: MediaHierarchyManager,
private val scrimTransitionController: LockscreenShadeScrimTransitionController,
private val keyguardTransitionControllerFactory:
- LockscreenShadeKeyguardTransitionController.Factory,
+ LockscreenShadeKeyguardTransitionController.Factory,
private val depthController: NotificationShadeDepthController,
private val context: Context,
private val splitShadeOverScrollerFactory: SplitShadeLockScreenOverScroller.Factory,
@@ -292,8 +292,7 @@
/** @return true if the interaction is accepted, false if it should be cancelled */
internal fun canDragDown(): Boolean {
return (statusBarStateController.state == StatusBarState.KEYGUARD ||
- nsslController.isInLockedDownShade()) &&
- (isQsFullyCollapsed || useSplitShade)
+ nsslController.isInLockedDownShade()) && (isQsFullyCollapsed || useSplitShade)
}
/** Called by the touch helper when when a gesture has completed all the way and released. */
@@ -885,7 +884,7 @@
isDraggingDown = false
isTrackpadReverseScroll = false
shadeRepository.setLegacyLockscreenShadeTracking(false)
- if (nsslFalsingFix() || migrateClocksToBlueprint()) {
+ if (nsslFalsingFix() || MigrateClocksToBlueprint.isEnabled) {
return true
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index e111525..8cdf60b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -42,7 +42,6 @@
import android.app.RemoteInput;
import android.app.RemoteInputHistoryItem;
import android.content.Context;
-import android.content.pm.ShortcutInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
@@ -133,7 +132,6 @@
public Uri remoteInputUri;
public ContentInfo remoteInputAttachment;
private Notification.BubbleMetadata mBubbleMetadata;
- private ShortcutInfo mShortcutInfo;
/**
* If {@link RemoteInput#getEditChoicesBeforeSending} is enabled, and the user is
@@ -168,10 +166,8 @@
private ListenerSet<OnSensitivityChangedListener> mOnSensitivityChangedListeners =
new ListenerSet<>();
- private boolean mAutoHeadsUp;
private boolean mPulseSupressed;
private int mBucket = BUCKET_ALERTING;
- @Nullable private Long mPendingAnimationDuration;
private boolean mIsMarkedForUserTriggeredMovement;
private boolean mIsHeadsUpEntry;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 8ed1ca2..2f577d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -23,7 +23,6 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.server.notification.Flags.screenshareNotificationHiding;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
import static com.android.systemui.Flags.nsslFalsingFix;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
@@ -71,6 +70,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -2090,7 +2090,7 @@
}
boolean horizontalSwipeWantsIt = false;
boolean scrollerWantsIt = false;
- if (nsslFalsingFix() || migrateClocksToBlueprint()) {
+ if (nsslFalsingFix() || MigrateClocksToBlueprint.isEnabled()) {
// Reverse the order relative to the else statement. onScrollTouch will reset on an
// UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes.
if (mLongPressedView == null && !mView.isBeingDragged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 6db6719..ecf737a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -48,6 +48,7 @@
private val sceneContainerFlags: SceneContainerFlags,
private val controller: NotificationStackScrollLayoutController,
private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
+ private val notificationStackViewBinder: NotificationStackViewBinder,
@Main private val mainImmediateDispatcher: CoroutineDispatcher,
) {
@@ -157,6 +158,10 @@
}
}
+ if (sceneContainerFlags.isEnabled()) {
+ disposables += notificationStackViewBinder.bindWhileAttached()
+ }
+
controller.setOnHeightChangedRunnable { viewModel.notificationStackChanged() }
disposables += DisposableHandle { controller.setOnHeightChangedRunnable(null) }
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 d32e88b..f76de04c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -27,7 +27,6 @@
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import static com.android.systemui.Flags.lightRevealMigration;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
import static com.android.systemui.Flags.newAodTransition;
import static com.android.systemui.Flags.predictiveBackSysui;
import static com.android.systemui.Flags.truncatedStatusBarIconsFix;
@@ -142,6 +141,7 @@
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
@@ -1470,7 +1470,7 @@
return (v, event) -> {
mAutoHideController.checkUserAutoHide(event);
mRemoteInputManager.checkRemoteInputOutside(event);
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mShadeController.onStatusBarTouch(event);
}
return getNotificationShadeWindowView().onTouchEvent(event);
@@ -2507,7 +2507,7 @@
mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
mDeviceInteractive = true;
- boolean isFlaggedOff = newAodTransition() && migrateClocksToBlueprint();
+ boolean isFlaggedOff = newAodTransition() && MigrateClocksToBlueprint.isEnabled();
if (!isFlaggedOff && shouldAnimateDozeWakeup()) {
// If this is false, the power button must be physically pressed in order to
// trigger fingerprint authentication.
@@ -3147,7 +3147,14 @@
public void onDozeAmountChanged(float linear, float eased) {
if (!lightRevealMigration()
&& !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
- mLightRevealScrim.setRevealAmount(1f - linear);
+ if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ // If wakeAndUnlocking, this is handled in AuthRippleInteractor
+ if (!mBiometricUnlockController.isWakeAndUnlock()) {
+ mLightRevealScrim.setRevealAmount(1f - linear);
+ }
+ } else {
+ mLightRevealScrim.setRevealAmount(1f - linear);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 94f62e0..f84efbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
import static com.android.systemui.Flags.newAodTransition;
-import static com.android.systemui.Flags.migrateClocksToBlueprint;
import android.content.Context;
import android.content.res.Resources;
@@ -41,6 +40,7 @@
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -545,7 +545,7 @@
return;
}
if (mScreenOffAnimationController.shouldAnimateAodIcons()) {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mAodIcons.setTranslationY(-mAodIconAppearTranslation);
}
mAodIcons.setAlpha(0);
@@ -557,14 +557,14 @@
.start();
} else {
mAodIcons.setAlpha(1.0f);
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mAodIcons.setTranslationY(0);
}
}
}
private void animateInAodIconTranslation() {
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mAodIcons.animate()
.setInterpolator(Interpolators.DECELERATE_QUINT)
.translationY(0)
@@ -667,7 +667,7 @@
}
} else {
mAodIcons.setAlpha(1.0f);
- if (!migrateClocksToBlueprint()) {
+ if (!MigrateClocksToBlueprint.isEnabled()) {
mAodIcons.setTranslationY(0);
}
mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 67d2299..f3c7090 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -19,9 +19,9 @@
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.systemui.DejankUtils
import com.android.systemui.Flags.lightRevealMigration
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
@@ -45,9 +45,7 @@
*/
private const val ANIMATE_IN_KEYGUARD_DELAY = 600L
-/**
- * Duration for the light reveal portion of the animation.
- */
+/** Duration for the light reveal portion of the animation. */
private const val LIGHT_REVEAL_ANIMATION_DURATION = 500L
/**
@@ -58,7 +56,9 @@
* and then animates in the AOD UI.
*/
@SysUISingleton
-class UnlockedScreenOffAnimationController @Inject constructor(
+class UnlockedScreenOffAnimationController
+@Inject
+constructor(
private val context: Context,
private val wakefulnessLifecycle: WakefulnessLifecycle,
private val statusBarStateControllerImpl: StatusBarStateControllerImpl,
@@ -95,52 +95,61 @@
*/
private var decidedToAnimateGoingToSleep: Boolean? = null
- private val lightRevealAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
- duration = LIGHT_REVEAL_ANIMATION_DURATION
- interpolator = Interpolators.LINEAR
- addUpdateListener {
- if (lightRevealMigration()) return@addUpdateListener
- if (lightRevealScrim.revealEffect !is CircleReveal) {
- lightRevealScrim.revealAmount = it.animatedValue as Float
- }
- if (lightRevealScrim.isScrimAlmostOccludes &&
- interactionJankMonitor.isInstrumenting(CUJ_SCREEN_OFF)) {
- // ends the instrument when the scrim almost occludes the screen.
- // because the following janky frames might not be perceptible.
- interactionJankMonitor.end(CUJ_SCREEN_OFF)
- }
- }
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationCancel(animation: Animator) {
- if (lightRevealMigration()) return
+ private val lightRevealAnimator =
+ ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = LIGHT_REVEAL_ANIMATION_DURATION
+ interpolator = Interpolators.LINEAR
+ addUpdateListener {
+ if (lightRevealMigration()) return@addUpdateListener
if (lightRevealScrim.revealEffect !is CircleReveal) {
- lightRevealScrim.revealAmount = 1f
+ lightRevealScrim.revealAmount = it.animatedValue as Float
+ }
+ if (
+ lightRevealScrim.isScrimAlmostOccludes &&
+ interactionJankMonitor.isInstrumenting(CUJ_SCREEN_OFF)
+ ) {
+ // ends the instrument when the scrim almost occludes the screen.
+ // because the following janky frames might not be perceptible.
+ interactionJankMonitor.end(CUJ_SCREEN_OFF)
}
}
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationCancel(animation: Animator) {
+ if (lightRevealMigration()) return
+ if (lightRevealScrim.revealEffect !is CircleReveal) {
+ lightRevealScrim.revealAmount = 1f
+ }
+ }
- override fun onAnimationEnd(animation: Animator) {
- lightRevealAnimationPlaying = false
- interactionJankMonitor.end(CUJ_SCREEN_OFF)
- }
+ override fun onAnimationEnd(animation: Animator) {
+ lightRevealAnimationPlaying = false
+ interactionJankMonitor.end(CUJ_SCREEN_OFF)
+ }
- override fun onAnimationStart(animation: Animator) {
- interactionJankMonitor.begin(
- notifShadeWindowControllerLazy.get().windowRootView, CUJ_SCREEN_OFF)
- }
- })
- }
+ override fun onAnimationStart(animation: Animator) {
+ interactionJankMonitor.begin(
+ notifShadeWindowControllerLazy.get().windowRootView,
+ CUJ_SCREEN_OFF
+ )
+ }
+ }
+ )
+ }
// FrameCallback used to delay starting the light reveal animation until the next frame
- private val startLightRevealCallback = namedRunnable("startLightReveal") {
- lightRevealAnimationPlaying = true
- lightRevealAnimator.start()
- }
-
- private val animatorDurationScaleObserver = object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- updateAnimatorDurationScale()
+ private val startLightRevealCallback =
+ namedRunnable("startLightReveal") {
+ lightRevealAnimationPlaying = true
+ lightRevealAnimator.start()
}
- }
+
+ private val animatorDurationScaleObserver =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ updateAnimatorDurationScale()
+ }
+ }
override fun initialize(
centralSurfaces: CentralSurfaces,
@@ -154,22 +163,21 @@
updateAnimatorDurationScale()
globalSettings.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
- /* notify for descendants */ false,
- animatorDurationScaleObserver)
+ Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+ /* notify for descendants */ false,
+ animatorDurationScaleObserver
+ )
wakefulnessLifecycle.addObserver(this)
}
fun updateAnimatorDurationScale() {
- animatorDurationScale = fixScale(
- globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f))
+ animatorDurationScale =
+ fixScale(globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f))
}
- override fun shouldDelayKeyguardShow(): Boolean =
- shouldPlayAnimation()
+ override fun shouldDelayKeyguardShow(): Boolean = shouldPlayAnimation()
- override fun isKeyguardShowDelayed(): Boolean =
- isAnimationPlaying()
+ override fun isKeyguardShowDelayed(): Boolean = isAnimationPlaying()
/**
* Animates in the provided keyguard view, ending in the same position that it will be in on
@@ -190,15 +198,21 @@
// We animate the Y properly separately using the PropertyAnimator, as the panel
// view also needs to update the end position.
PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y)
- PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY,
- AnimationProperties().setDuration(duration.toLong()),
- true /* animate */)
+ PropertyAnimator.setProperty(
+ keyguardView,
+ AnimatableProperty.Y,
+ currentY,
+ AnimationProperties().setDuration(duration.toLong()),
+ true /* animate */
+ )
// Cancel any existing CUJs before starting the animation
interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.ALPHA)
PropertyAnimator.setProperty(
- keyguardView, AnimatableProperty.ALPHA, 1f,
+ keyguardView,
+ AnimatableProperty.ALPHA,
+ 1f,
AnimationProperties()
.setDelay(0)
.setDuration(duration.toLong())
@@ -230,13 +244,14 @@
interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
}
.setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_SLOW_IN),
- true /* animate */)
- val builder = InteractionJankMonitor.Configuration.Builder
- .withView(
+ true /* animate */
+ )
+ val builder =
+ InteractionJankMonitor.Configuration.Builder.withView(
InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD,
checkNotNull(notifShadeWindowControllerLazy.get().windowRootView)
- )
- .setTag(statusBarStateControllerImpl.getClockId())
+ )
+ .setTag(statusBarStateControllerImpl.getClockId())
interactionJankMonitor.begin(builder)
}
@@ -284,25 +299,34 @@
// chance of missing the first frame, so to mitigate this we should start the animation
// on the next frame.
DejankUtils.postAfterTraversal(startLightRevealCallback)
- handler.postDelayed({
- // Only run this callback if the device is sleeping (not interactive). This callback
- // is removed in onStartedWakingUp, but since that event is asynchronously
- // dispatched, a race condition could make it possible for this callback to be run
- // as the device is waking up. That results in the AOD UI being shown while we wake
- // up, with unpredictable consequences.
- if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) &&
- shouldAnimateInKeyguard) {
- if (!migrateClocksToBlueprint()) {
- // Tracking this state should no longer be relevant, as the isInteractive
- // check covers it
- aodUiAnimationPlaying = true
- }
+ handler.postDelayed(
+ {
+ // Only run this callback if the device is sleeping (not interactive). This
+ // callback
+ // is removed in onStartedWakingUp, but since that event is asynchronously
+ // dispatched, a race condition could make it possible for this callback to be
+ // run
+ // as the device is waking up. That results in the AOD UI being shown while we
+ // wake
+ // up, with unpredictable consequences.
+ if (
+ !powerManager.isInteractive(Display.DEFAULT_DISPLAY) &&
+ shouldAnimateInKeyguard
+ ) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
+ // Tracking this state should no longer be relevant, as the
+ // isInteractive
+ // check covers it
+ aodUiAnimationPlaying = true
+ }
- // Show AOD. That'll cause the KeyguardVisibilityHelper to call
- // #animateInKeyguard.
- shadeViewController.showAodUi()
- }
- }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())
+ // Show AOD. That'll cause the KeyguardVisibilityHelper to call
+ // #animateInKeyguard.
+ shadeViewController.showAodUi()
+ }
+ },
+ (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong()
+ )
return true
} else {
@@ -335,8 +359,12 @@
}
// If animations are disabled system-wide, don't play this one either.
- if (Settings.Global.getString(
- context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE) == "0") {
+ if (
+ Settings.Global.getString(
+ context.contentResolver,
+ Settings.Global.ANIMATOR_DURATION_SCALE
+ ) == "0"
+ ) {
return false
}
@@ -360,8 +388,10 @@
// If we're not allowed to rotate the keyguard, it can only be displayed in zero-degree
// portrait. If we're in another orientation, disable the screen off animation so we don't
// animate in the keyguard AOD UI sideways or upside down.
- if (!keyguardStateController.isKeyguardScreenRotationAllowed &&
- context.display?.rotation != Surface.ROTATION_0) {
+ if (
+ !keyguardStateController.isKeyguardScreenRotationAllowed &&
+ context.display?.rotation != Surface.ROTATION_0
+ ) {
return false
}
@@ -380,23 +410,18 @@
return isScreenOffLightRevealAnimationPlaying() || aodUiAnimationPlaying
}
- override fun shouldAnimateInKeyguard(): Boolean =
- shouldAnimateInKeyguard
+ override fun shouldAnimateInKeyguard(): Boolean = shouldAnimateInKeyguard
- override fun shouldHideScrimOnWakeUp(): Boolean =
- isScreenOffLightRevealAnimationPlaying()
+ override fun shouldHideScrimOnWakeUp(): Boolean = isScreenOffLightRevealAnimationPlaying()
override fun overrideNotificationsDozeAmount(): Boolean =
shouldPlayUnlockedScreenOffAnimation() && isAnimationPlaying()
- override fun shouldShowAodIconsWhenShade(): Boolean =
- isAnimationPlaying()
+ override fun shouldShowAodIconsWhenShade(): Boolean = isAnimationPlaying()
- override fun shouldAnimateAodIcons(): Boolean =
- shouldPlayUnlockedScreenOffAnimation()
+ override fun shouldAnimateAodIcons(): Boolean = shouldPlayUnlockedScreenOffAnimation()
- override fun shouldPlayAnimation(): Boolean =
- shouldPlayUnlockedScreenOffAnimation()
+ override fun shouldPlayAnimation(): Boolean = shouldPlayUnlockedScreenOffAnimation()
/**
* Whether the light reveal animation is playing. The second part of the screen off animation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index 087e100..7a57027 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -42,6 +42,10 @@
import com.android.systemui.qs.tiles.impl.uimodenight.domain.interactor.UiModeNightTileDataInteractor
import com.android.systemui.qs.tiles.impl.uimodenight.domain.interactor.UiModeNightTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.uimodenight.domain.model.UiModeNightTileModel
+import com.android.systemui.qs.tiles.impl.work.domain.interactor.WorkModeTileDataInteractor
+import com.android.systemui.qs.tiles.impl.work.domain.interactor.WorkModeTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.work.domain.model.WorkModeTileModel
+import com.android.systemui.qs.tiles.impl.work.ui.WorkModeTileMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
@@ -69,6 +73,7 @@
const val LOCATION_TILE_SPEC = "location"
const val ALARM_TILE_SPEC = "alarm"
const val UIMODENIGHT_TILE_SPEC = "dark"
+ const val WORK_MODE_TILE_SPEC = "work"
/** Inject flashlight config */
@Provides
@@ -197,6 +202,38 @@
stateInteractor,
mapper,
)
+
+ /** Inject work mode tile config */
+ @Provides
+ @IntoMap
+ @StringKey(WORK_MODE_TILE_SPEC)
+ fun provideWorkModeTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(WORK_MODE_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status,
+ labelRes = R.string.quick_settings_work_mode_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject work mode into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(WORK_MODE_TILE_SPEC)
+ fun provideWorkModeTileViewModel(
+ factory: QSTileViewModelFactory.Static<WorkModeTileModel>,
+ mapper: WorkModeTileMapper,
+ stateInteractor: WorkModeTileDataInteractor,
+ userActionInteractor: WorkModeTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(WORK_MODE_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
}
/** Inject FlashlightTile into tileMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 18ec68b..1f4c3cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import static android.permission.flags.Flags.sensitiveNotificationAppProtection;
import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
import static com.android.server.notification.Flags.screenshareNotificationHiding;
@@ -23,6 +24,7 @@
import android.annotation.MainThread;
import android.app.IActivityManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.database.ExecutorContentObserver;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
@@ -33,6 +35,9 @@
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -52,6 +57,7 @@
implements SensitiveNotificationProtectionController {
private static final String LOG_TAG = "SNPC";
private final SensitiveNotificationProtectionControllerLogger mLogger;
+ private final PackageManager mPackageManager;
private final ArraySet<String> mExemptPackages = new ArraySet<>();
private final ListenerSet<Runnable> mListeners = new ListenerSet<>();
private volatile MediaProjectionInfo mProjection;
@@ -64,17 +70,7 @@
public void onStart(MediaProjectionInfo info) {
Trace.beginSection("SNPC.onProjectionStart");
try {
- if (mDisableScreenShareProtections) {
- Log.w(LOG_TAG,
- "Screen share protections disabled, ignoring projectionstart");
- mLogger.logProjectionStart(false, info.getPackageName());
- return;
- }
-
- // Only enable sensitive content protection if sharing full screen
- // Launch cookie only set (non-null) if sharing single app/task
- updateProjectionStateAndNotifyListeners(
- (info.getLaunchCookie() == null) ? info : null);
+ updateProjectionStateAndNotifyListeners(info);
mLogger.logProjectionStart(isSensitiveStateActive(), info.getPackageName());
} finally {
Trace.endSection();
@@ -99,10 +95,12 @@
GlobalSettings settings,
MediaProjectionManager mediaProjectionManager,
IActivityManager activityManager,
+ PackageManager packageManager,
@Main Handler mainHandler,
@Background Executor bgExecutor,
SensitiveNotificationProtectionControllerLogger logger) {
mLogger = logger;
+ mPackageManager = packageManager;
if (!screenshareNotificationHiding()) {
return;
@@ -168,7 +166,7 @@
mExemptPackages.addAll(exemptPackages);
if (mProjection != null) {
- mListeners.forEach(Runnable::run);
+ updateProjectionStateAndNotifyListeners(mProjection);
}
}
@@ -177,13 +175,13 @@
* listeners
*/
@MainThread
- private void updateProjectionStateAndNotifyListeners(MediaProjectionInfo info) {
+ private void updateProjectionStateAndNotifyListeners(@Nullable MediaProjectionInfo info) {
Assert.isMainThread();
// capture previous state
boolean wasSensitive = isSensitiveStateActive();
// update internal state
- mProjection = info;
+ mProjection = getNonExemptProjectionInfo(info);
// if either previous or new state is sensitive, notify listeners.
if (wasSensitive || isSensitiveStateActive()) {
@@ -191,6 +189,36 @@
}
}
+ private MediaProjectionInfo getNonExemptProjectionInfo(@Nullable MediaProjectionInfo info) {
+ if (mDisableScreenShareProtections) {
+ Log.w(LOG_TAG, "Screen share protections disabled");
+ return null;
+ } else if (info != null && mExemptPackages.contains(info.getPackageName())) {
+ Log.w(LOG_TAG, "Screen share protections exempt for package " + info.getPackageName());
+ return null;
+ } else if (info != null && canRecordSensitiveContent(info.getPackageName())) {
+ Log.w(LOG_TAG, "Screen share protections exempt for package " + info.getPackageName()
+ + " via permission");
+ return null;
+ } else if (info != null && info.getLaunchCookie() != null) {
+ // Only enable sensitive content protection if sharing full screen
+ // Launch cookie only set (non-null) if sharing single app/task
+ Log.w(LOG_TAG, "Screen share protections exempt for single app screenshare");
+ return null;
+ }
+ return info;
+ }
+
+ private boolean canRecordSensitiveContent(@NonNull String packageName) {
+ // RECORD_SENSITIVE_CONTENT is flagged api on sensitiveNotificationAppProtection
+ if (sensitiveNotificationAppProtection()) {
+ return mPackageManager.checkPermission(
+ android.Manifest.permission.RECORD_SENSITIVE_CONTENT, packageName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+ return false;
+ }
+
@Override
public void registerSensitiveStateListener(Runnable onSensitiveStateChanged) {
mListeners.addIfAbsent(onSensitiveStateChanged);
@@ -201,15 +229,9 @@
mListeners.remove(onSensitiveStateChanged);
}
- // TODO(b/323396693): opportunity for optimization
@Override
public boolean isSensitiveStateActive() {
- MediaProjectionInfo projection = mProjection;
- if (projection == null) {
- return false;
- }
-
- return !mExemptPackages.contains(projection.getPackageName());
+ return mProjection != null;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/ManagedProfileControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/ManagedProfileControllerExt.kt
new file mode 100644
index 0000000..7a2f9b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/ManagedProfileControllerExt.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.statusbar.phone.ManagedProfileController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+val ManagedProfileController.hasActiveWorkProfile: Flow<Boolean>
+ get() = conflatedCallbackFlow {
+ val callback =
+ object : ManagedProfileController.Callback {
+ override fun onManagedProfileChanged() {
+ trySend(hasActiveProfile())
+ }
+ override fun onManagedProfileRemoved() {
+ // no-op, because the other callback will also be called.
+ }
+ }
+ addCallback(callback) // calls onManagedProfileChanged
+ awaitClose { removeCallback(callback) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 7931fab..e48b639 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -363,8 +363,8 @@
}, mSysUiMainExecutor);
mCommandQueue.addCallback(new CommandQueue.Callbacks() {
@Override
- public void enterDesktop(int displayId) {
- desktopMode.enterDesktop(displayId);
+ public void moveFocusedTaskToDesktop(int displayId) {
+ desktopMode.moveFocusedTaskToDesktop(displayId);
}
@Override
public void moveFocusedTaskToFullscreen(int displayId) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
new file mode 100644
index 0000000..4215b8c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.work.ui
+
+import android.app.admin.DevicePolicyResources
+import android.app.admin.DevicePolicyResourcesManager
+import android.app.admin.devicePolicyManager
+import android.graphics.drawable.TestStubDrawable
+import android.service.quicksettings.Tile
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.work.domain.model.WorkModeTileModel
+import com.android.systemui.qs.tiles.impl.work.qsWorkModeTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WorkModeTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val qsTileConfig = kosmos.qsWorkModeTileConfig
+ private val devicePolicyManager = kosmos.devicePolicyManager
+ private val testLabel = context.getString(R.string.quick_settings_work_mode_label)
+ private val devicePolicyResourceManager = mock<DevicePolicyResourcesManager>()
+ private lateinit var mapper: WorkModeTileMapper
+
+ @Before
+ fun setup() {
+ whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourceManager)
+ whenever(
+ devicePolicyResourceManager.getString(
+ eq(DevicePolicyResources.Strings.SystemUi.QS_WORK_PROFILE_LABEL),
+ any()
+ )
+ )
+ .thenReturn(testLabel)
+ mapper =
+ WorkModeTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(
+ com.android.internal.R.drawable.stat_sys_managed_profile_status,
+ TestStubDrawable()
+ )
+ }
+ .resources,
+ context.theme,
+ devicePolicyManager
+ )
+ }
+
+ @Test
+ fun mapsDisabledDataToInactiveState() {
+ val isEnabled = false
+
+ val actualState: QSTileState =
+ mapper.map(qsTileConfig, WorkModeTileModel.HasActiveProfile(isEnabled))
+
+ val expectedState = createWorkModeTileState(QSTileState.ActivationState.INACTIVE)
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapsEnabledDataToActiveState() {
+ val isEnabled = true
+
+ val actualState: QSTileState =
+ mapper.map(qsTileConfig, WorkModeTileModel.HasActiveProfile(isEnabled))
+
+ val expectedState = createWorkModeTileState(QSTileState.ActivationState.ACTIVE)
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapsNoActiveProfileDataToUnavailableState() {
+ val actualState: QSTileState = mapper.map(qsTileConfig, WorkModeTileModel.NoActiveProfile)
+
+ val expectedState = createWorkModeTileState(QSTileState.ActivationState.UNAVAILABLE)
+ QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
+ }
+
+ private fun createWorkModeTileState(
+ activationState: QSTileState.ActivationState,
+ ): QSTileState {
+ val label = testLabel
+ return QSTileState(
+ icon = {
+ Icon.Loaded(
+ context.getDrawable(
+ com.android.internal.R.drawable.stat_sys_managed_profile_status
+ )!!,
+ null
+ )
+ },
+ label = label,
+ activationState = activationState,
+ secondaryLabel =
+ if (activationState == QSTileState.ActivationState.INACTIVE) {
+ context.getString(R.string.quick_settings_work_mode_paused_state)
+ } else if (activationState == QSTileState.ActivationState.UNAVAILABLE) {
+ context.resources
+ .getStringArray(R.array.tile_states_work)[Tile.STATE_UNAVAILABLE]
+ } else {
+ ""
+ },
+ supportedActions =
+ if (activationState == QSTileState.ActivationState.UNAVAILABLE) {
+ setOf()
+ } else {
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ },
+ contentDescription = label,
+ stateDescription = null,
+ sideViewIcon = QSTileState.SideViewIcon.None,
+ enabledState = QSTileState.EnabledState.ENABLED,
+ expandedAccessibilityClassName = Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index a4f88fb..10d2191 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -49,7 +49,6 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
@@ -63,6 +62,7 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -130,6 +130,7 @@
@RunWith(AndroidTestingRunner.class)
public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
+ protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock private NotificationGutsManager mNotificationGutsManager;
@Mock private NotificationsController mNotificationsController;
@@ -167,7 +168,6 @@
@Mock private SceneContainerFlags mSceneContainerFlags;
@Mock private Provider<WindowRootView> mWindowRootView;
@Mock private NotificationStackAppearanceInteractor mNotificationStackAppearanceInteractor;
- @Mock private InteractionJankMonitor mJankMonitor;
private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(),
logcatLogBuffer());
private final NotificationStackScrollLogger mLogger = new NotificationStackScrollLogger(
@@ -1030,7 +1030,7 @@
mSceneContainerFlags,
mWindowRootView,
mNotificationStackAppearanceInteractor,
- mJankMonitor,
+ mKosmos.getInteractionJankMonitor(),
mStackLogger,
mLogger,
mNotificationStackSizeCalculator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
index 933b5b5..358709f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy
import android.app.IActivityManager
+import android.content.pm.PackageManager
import android.media.projection.MediaProjectionManager
import android.os.Handler
import android.platform.test.annotations.DisableFlags
@@ -44,6 +45,7 @@
@Mock private lateinit var handler: Handler
@Mock private lateinit var activityManager: IActivityManager
@Mock private lateinit var mediaProjectionManager: MediaProjectionManager
+ @Mock private lateinit var packageManager: PackageManager
private lateinit var controller: SensitiveNotificationProtectionControllerImpl
@Before
@@ -56,6 +58,7 @@
FakeGlobalSettings(),
mediaProjectionManager,
activityManager,
+ packageManager,
handler,
FakeExecutor(FakeSystemClock()),
logger
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index 4b4e315..7dfe6d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -25,9 +25,14 @@
import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE
+import android.content.pm.PackageManager
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
+import android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION
import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -48,9 +53,11 @@
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
@@ -64,10 +71,13 @@
@RunWithLooper
@EnableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
+ @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
private val logger = SensitiveNotificationProtectionControllerLogger(logcatLogBuffer())
@Mock private lateinit var activityManager: IActivityManager
@Mock private lateinit var mediaProjectionManager: MediaProjectionManager
+ @Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo
@Mock private lateinit var listener1: Runnable
@Mock private lateinit var listener2: Runnable
@@ -87,6 +97,9 @@
whenever(activityManager.bugreportWhitelistedPackages)
.thenReturn(listOf(BUGREPORT_PACKAGE_NAME))
+ whenever(packageManager.checkPermission(anyString(), anyString()))
+ .thenReturn(PackageManager.PERMISSION_DENIED)
+
executor = FakeExecutor(FakeSystemClock())
globalSettings = FakeGlobalSettings()
controller =
@@ -95,6 +108,7 @@
globalSettings,
mediaProjectionManager,
activityManager,
+ packageManager,
mockExecutorHandler(executor),
executor,
logger
@@ -237,6 +251,36 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
+ fun isSensitiveStateActive_projectionActive_permissionExempt_flagDisabled_true() {
+ whenever(
+ packageManager.checkPermission(
+ android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
+ mediaProjectionInfo.packageName
+ )
+ )
+ .thenReturn(PackageManager.PERMISSION_GRANTED)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ assertTrue(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
+ fun isSensitiveStateActive_projectionActive_permissionExempt_false() {
+ whenever(
+ packageManager.checkPermission(
+ android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
+ mediaProjectionInfo.packageName
+ )
+ )
+ .thenReturn(PackageManager.PERMISSION_GRANTED)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
fun isSensitiveStateActive_projectionActive_bugReportHandlerExempt_false() {
whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
mediaProjectionCallback.onStart(mediaProjectionInfo)
@@ -309,6 +353,40 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
+ fun shouldProtectNotification_projectionActive_permissionExempt_flagDisabled_true() {
+ whenever(
+ packageManager.checkPermission(
+ android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
+ mediaProjectionInfo.packageName
+ )
+ )
+ .thenReturn(PackageManager.PERMISSION_GRANTED)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
+ fun shouldProtectNotification_projectionActive_permissionExempt_false() {
+ whenever(
+ packageManager.checkPermission(
+ android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
+ mediaProjectionInfo.packageName
+ )
+ )
+ .thenReturn(PackageManager.PERMISSION_GRANTED)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
fun shouldProtectNotification_projectionActive_bugReportHandlerExempt_false() {
whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
mediaProjectionCallback.onStart(mediaProjectionInfo)
@@ -327,6 +405,7 @@
assertFalse(controller.shouldProtectNotification(notificationEntry))
}
+
@Test
fun shouldProtectNotification_projectionActive_publicNotification_false() {
mediaProjectionCallback.onStart(mediaProjectionInfo)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..1b6fa06
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModelKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 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(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+var Kosmos.goneToLockscreenTransitionViewModel by Fixture {
+ GoneToLockscreenTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index a84899e..b91aafe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -52,6 +52,7 @@
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
goneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel,
+ goneToLockscreenTransitionViewModel = goneToLockscreenTransitionViewModel,
lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/work/WorkModeTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/work/WorkModeTileKosmos.kt
new file mode 100644
index 0000000..c04c5ed
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/work/WorkModeTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.work
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.policy.PolicyModule
+
+val Kosmos.qsWorkModeTileConfig by
+ Kosmos.Fixture { PolicyModule.provideWorkModeTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
index 18b07cf..59adb11 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
@@ -19,24 +19,65 @@
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.ManagedProfileController.Callback;
+import java.util.ArrayList;
+import java.util.List;
+
public class FakeManagedProfileController extends BaseLeakChecker<Callback> implements
ManagedProfileController {
+
+ private List<Callback> mCallbackList = new ArrayList<>();
+ private boolean mIsEnabled = false;
+ private boolean mHasActiveProfile = false;
+
public FakeManagedProfileController(LeakCheck test) {
super(test, "profile");
}
@Override
+ public void addCallback(Callback cb) {
+ mCallbackList.add(cb);
+ cb.onManagedProfileChanged();
+ }
+
+ @Override
+ public void removeCallback(Callback cb) {
+ mCallbackList.remove(cb);
+ }
+
+ @Override
public void setWorkModeEnabled(boolean enabled) {
+ if (mIsEnabled != enabled) {
+ mIsEnabled = enabled;
+ for (Callback cb: mCallbackList) {
+ cb.onManagedProfileChanged();
+ }
+ }
}
@Override
public boolean hasActiveProfile() {
- return false;
+ return mHasActiveProfile;
+ }
+
+ /**
+ * Triggers onManagedProfileChanged on callbacks when value flips.
+ */
+ public void setHasActiveProfile(boolean hasActiveProfile) {
+ if (mHasActiveProfile != hasActiveProfile) {
+ mHasActiveProfile = hasActiveProfile;
+ for (Callback cb: mCallbackList) {
+ cb.onManagedProfileChanged();
+ if (!hasActiveProfile) {
+ cb.onManagedProfileRemoved();
+ }
+ }
+ }
+
}
@Override
public boolean isWorkModeEnabled() {
- return false;
+ return mIsEnabled;
}
}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java
index 81ad31e..61ec7b4 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java
@@ -383,9 +383,21 @@
// Assume false for now, because we don't support writing FDs yet.
return false;
}
+
public static boolean nativeHasFileDescriptorsInRange(
long nativePtr, int offset, int length) {
// Assume false for now, because we don't support writing FDs yet.
return false;
}
+
+ public static boolean nativeHasBinders(long nativePtr) {
+ // Assume false for now, because we don't support adding binders.
+ return false;
+ }
+
+ public static boolean nativeHasBindersInRange(
+ long nativePtr, int offset, int length) {
+ // Assume false for now, because we don't support writing FDs yet.
+ return false;
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 880a687..3e7682a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2703,11 +2703,13 @@
Map<ComponentName, AccessibilityServiceConnection> componentNameToServiceMap =
userState.mComponentNameToServiceMap;
boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId);
+ Set<ComponentName> installedComponentNames = new HashSet<>();
for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
ComponentName componentName = ComponentName.unflattenFromString(
installedService.getId());
+ installedComponentNames.add(componentName);
AccessibilityServiceConnection service = componentNameToServiceMap.get(componentName);
@@ -2767,6 +2769,28 @@
audioManager.setAccessibilityServiceUids(mTempIntArray);
}
mActivityTaskManagerService.setAccessibilityServiceUids(mTempIntArray);
+ final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
+ boolean anyServiceRemoved = false;
+ while (it.hasNext()) {
+ final ComponentName comp = it.next();
+ if (!installedComponentNames.contains(comp)) {
+ it.remove();
+ userState.mTouchExplorationGrantedServices.remove(comp);
+ anyServiceRemoved = true;
+ }
+ }
+ if (anyServiceRemoved) {
+ // Update the enabled services setting.
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mEnabledServices,
+ userState.mUserId);
+ // Update the touch exploration granted services setting.
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ userState.mTouchExplorationGrantedServices,
+ userState.mUserId);
+ }
updateAccessibilityEnabledSettingLocked(userState);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index c570d65..d307484 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -79,6 +79,8 @@
private static int sNextWindowId;
+ private final Region mTmpRegion = new Region();
+
private final Object mLock;
private final Handler mHandler;
private final WindowManagerInternal mWindowManagerInternal;
@@ -613,7 +615,7 @@
}
// If the window is completely covered by other windows - ignore.
- if (unaccountedSpace.quickReject(regionInScreen)) {
+ if (!mTmpRegion.op(unaccountedSpace, regionInScreen, Region.Op.INTERSECT)) {
return false;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 8244d20..3ec6e47 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -23,6 +23,7 @@
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
@@ -82,6 +83,8 @@
import android.hardware.input.VirtualStylusMotionEvent;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioMix;
import android.os.Binder;
import android.os.IBinder;
import android.os.LocaleList;
@@ -1063,6 +1066,37 @@
}
@Override
+ public boolean hasCustomAudioInputSupport() throws RemoteException {
+ if (!Flags.vdmPublicApis()) {
+ return false;
+ }
+
+ if (!android.media.audiopolicy.Flags.audioMixTestApi()) {
+ return false;
+ }
+ if (!android.media.audiopolicy.Flags.recordAudioDeviceAwarePermission()) {
+ return false;
+ }
+
+ if (getDevicePolicy(POLICY_TYPE_AUDIO) == VirtualDeviceParams.DEVICE_POLICY_DEFAULT) {
+ return false;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+ for (AudioMix mix : audioManager.getRegisteredPolicyMixes()) {
+ if (mix.matchesVirtualDeviceId(getDeviceId())
+ && mix.getMixType() == AudioMix.MIX_TYPE_RECORDERS) {
+ return true;
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return false;
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
String indent = " ";
fout.println(" VirtualDevice: ");
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 6b5ba96..2607ed3 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -1297,15 +1297,19 @@
@Override
public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
- RemoteContentProtectionService service = createRemoteContentProtectionService();
- if (service == null) {
- return;
- }
- try {
- service.onLoginDetected(events);
- } catch (Exception ex) {
- Slog.e(TAG, "Failed to call remote service", ex);
- }
+ Binder.withCleanCallingIdentity(
+ () -> {
+ RemoteContentProtectionService service =
+ createRemoteContentProtectionService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.onLoginDetected(events);
+ } catch (Exception ex) {
+ Slog.e(TAG, "Failed to call remote service", ex);
+ }
+ });
}
}
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index cc40940..589d8b3 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -69,6 +69,8 @@
final Object mSensitiveContentProtectionLock = new Object();
+ private final ArraySet<PackageInfo> mPackagesShowingSensitiveContent = new ArraySet<>();
+
@GuardedBy("mSensitiveContentProtectionLock")
private boolean mProjectionActive = false;
@@ -205,6 +207,10 @@
if (sensitiveNotificationAppProtection()) {
updateAppsThatShouldBlockScreenCapture();
}
+
+ if (sensitiveContentAppProtection() && mPackagesShowingSensitiveContent.size() > 0) {
+ mWindowManager.addBlockScreenCaptureForApps(mPackagesShowingSensitiveContent);
+ }
}
}
@@ -354,17 +360,27 @@
void setSensitiveContentProtection(IBinder windowToken, String packageName, int uid,
boolean isShowingSensitiveContent) {
synchronized (mSensitiveContentProtectionLock) {
+ // The window token distinguish this package from packages added for notifications.
+ PackageInfo packageInfo = new PackageInfo(packageName, uid, windowToken);
+ // track these packages to protect when screen share starts.
+ if (isShowingSensitiveContent) {
+ mPackagesShowingSensitiveContent.add(packageInfo);
+ if (mPackagesShowingSensitiveContent.size() > 100) {
+ Log.w(TAG, "Unexpectedly large number of sensitive windows, count: "
+ + mPackagesShowingSensitiveContent.size());
+ }
+ } else {
+ mPackagesShowingSensitiveContent.remove(packageInfo);
+ }
if (!mProjectionActive) {
return;
}
if (DEBUG) {
- Log.d(TAG, "setSensitiveContentProtection - windowToken=" + windowToken
- + ", package=" + packageName + ", uid=" + uid
- + ", isShowingSensitiveContent=" + isShowingSensitiveContent);
+ Log.d(TAG, "setSensitiveContentProtection - current package=" + packageInfo
+ + ", isShowingSensitiveContent=" + isShowingSensitiveContent
+ + ", sensitive packages=" + mPackagesShowingSensitiveContent);
}
- // The window token distinguish this package from packages added for notifications.
- PackageInfo packageInfo = new PackageInfo(packageName, uid, windowToken);
ArraySet<PackageInfo> packageInfos = new ArraySet<>();
packageInfos.add(packageInfo);
if (isShowingSensitiveContent) {
@@ -392,6 +408,12 @@
verifyCallingPackage(callingUid, packageName);
final long identity = Binder.clearCallingIdentity();
try {
+ if (isShowingSensitiveContent
+ && mWindowManager.getWindowName(windowToken) == null) {
+ Log.e(TAG, "window token is not know to WMS, can't apply protection,"
+ + " token: " + windowToken + ", package: " + packageName);
+ return;
+ }
SensitiveContentProtectionManagerService.this.setSensitiveContentProtection(
windowToken, packageName, callingUid, isShowingSensitiveContent);
} finally {
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 91cfb8f..e676b1f 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -281,7 +281,7 @@
* For {@link BroadcastQueueModernImpl}: Maximum number of outgoing broadcasts from a
* freezable process that will be allowed before killing the process.
*/
- public long MAX_FROZEN_OUTGOING_BROADCASTS = DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS;
+ public int MAX_FROZEN_OUTGOING_BROADCASTS = DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS;
private static final String KEY_MAX_FROZEN_OUTGOING_BROADCASTS =
"max_frozen_outgoing_broadcasts";
private static final int DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS = 32;
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index e98e1ba..ed3cd1e 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -277,6 +277,10 @@
mOutgoingBroadcasts.clear();
}
+ public void clearOutgoingBroadcasts() {
+ mOutgoingBroadcasts.clear();
+ }
+
/**
* Enqueue the given broadcast to be dispatched to this process at some
* future point in time. The target receiver is indicated by the given index
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a6f6b34..c082889 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -166,7 +166,7 @@
/**
* Map from UID to per-process broadcast queues. If a UID hosts more than
* one process, each additional process is stored as a linked list using
- * {@link BroadcastProcessQueue#next}.
+ * {@link BroadcastProcessQueue#processNameNext}.
*
* @see #getProcessQueue
* @see #getOrCreateProcessQueue
@@ -661,6 +661,10 @@
final BroadcastProcessQueue queue = getProcessQueue(app);
if (queue != null) {
setQueueProcess(queue, app);
+ // Outgoing broadcasts should be cleared when the process dies but there have been
+ // issues due to AMS not always informing the BroadcastQueue of process deaths.
+ // So, clear them when a new process starts as well.
+ queue.clearOutgoingBroadcasts();
}
boolean didSomething = false;
@@ -730,6 +734,8 @@
demoteFromRunningLocked(queue);
}
+ queue.clearOutgoingBroadcasts();
+
// Skip any pending registered receivers, since the old process
// would never be around to receive them
boolean didSomething = queue.forEachMatchingBroadcast((r, i) -> {
@@ -781,8 +787,11 @@
final BroadcastProcessQueue queue = getOrCreateProcessQueue(
r.callerApp.processName, r.callerApp.uid);
if (queue.getOutgoingBroadcastCount() >= mConstants.MAX_FROZEN_OUTGOING_BROADCASTS) {
- // TODO: Kill the process if the outgoing broadcasts count is
- // beyond a certain limit.
+ r.callerApp.killLocked("Too many outgoing broadcasts in cached state",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_EXCESSIVE_OUTGOING_BROADCASTS_WHILE_CACHED,
+ true /* noisy */);
+ return;
}
queue.enqueueOutgoingBroadcast(r);
mHistory.onBroadcastFrozenLocked(r);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index de000bf..53c0f58 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6854,15 +6854,6 @@
ringerMode = RINGER_MODE_SILENT;
}
}
- } else if (mIsSingleVolume && (direction == AudioManager.ADJUST_TOGGLE_MUTE
- || direction == AudioManager.ADJUST_MUTE)) {
- if (mHasVibrator) {
- ringerMode = RINGER_MODE_VIBRATE;
- } else {
- ringerMode = RINGER_MODE_SILENT;
- }
- // Setting the ringer mode will toggle mute
- result &= ~FLAG_ADJUST_VOLUME;
}
break;
case RINGER_MODE_VIBRATE:
@@ -6871,11 +6862,8 @@
"but no vibrator is present");
break;
}
- if ((direction == AudioManager.ADJUST_LOWER)) {
- // This is the case we were muted with the volume turned up
- if (mIsSingleVolume && oldIndex >= 2 * step && isMuted) {
- ringerMode = RINGER_MODE_NORMAL;
- } else if (mPrevVolDirection != AudioManager.ADJUST_LOWER) {
+ if (direction == AudioManager.ADJUST_LOWER) {
+ if (mPrevVolDirection != AudioManager.ADJUST_LOWER) {
if (mVolumePolicy.volumeDownToEnterSilent) {
final long diff = SystemClock.uptimeMillis()
- mLoweredFromNormalToVibrateTime;
@@ -6895,10 +6883,7 @@
result &= ~FLAG_ADJUST_VOLUME;
break;
case RINGER_MODE_SILENT:
- if (mIsSingleVolume && direction == AudioManager.ADJUST_LOWER && oldIndex >= 2 * step && isMuted) {
- // This is the case we were muted with the volume turned up
- ringerMode = RINGER_MODE_NORMAL;
- } else if (direction == AudioManager.ADJUST_RAISE
+ if (direction == AudioManager.ADJUST_RAISE
|| direction == AudioManager.ADJUST_TOGGLE_MUTE
|| direction == AudioManager.ADJUST_UNMUTE) {
if (!mVolumePolicy.volumeUpToExitSilent) {
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 136ab42c..31ce630 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -441,14 +441,17 @@
private void sendOnStartInputResult(
IInputMethodClient client, InputBindResult res, int startInputSeq) {
- InputMethodManagerService service = (InputMethodManagerService) mInner;
- final ClientState cs = service.getClientState(client);
- if (cs != null && cs.mClient != null) {
- cs.mClient.onStartInputResult(res, startInputSeq);
- } else {
- // client is unbound.
- Slog.i(TAG, "Client that requested startInputOrWindowGainedFocus is no longer"
- + " bound. InputBindResult: " + res + " for startInputSeq: " + startInputSeq);
+ synchronized (ImfLock.class) {
+ InputMethodManagerService service = (InputMethodManagerService) mInner;
+ final ClientState cs = service.getClientState(client);
+ if (cs != null && cs.mClient != null) {
+ cs.mClient.onStartInputResult(res, startInputSeq);
+ } else {
+ // client is unbound.
+ Slog.i(TAG, "Client that requested startInputOrWindowGainedFocus is no longer"
+ + " bound. InputBindResult: " + res + " for startInputSeq: "
+ + startInputSeq);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f655455..76bf8fd 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3508,7 +3508,7 @@
if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
if (statusbar != null) {
- statusbar.enterDesktop(getTargetDisplayIdForKeyEvent(event));
+ statusbar.moveFocusedTaskToDesktop(getTargetDisplayIdForKeyEvent(event));
logKeyboardSystemsEvent(event, KeyboardLogEvent.DESKTOP_MODE);
return true;
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index f7c236a..2ff3861 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -267,7 +267,7 @@
void removeQsTile(ComponentName tile);
/**
- * Called when requested to enter desktop from an app.
+ * Called when requested to enter desktop from a focused app.
*/
- void enterDesktop(int displayId);
+ void moveFocusedTaskToDesktop(int displayId);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 7b3e237..cca5beb 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -838,15 +838,17 @@
} catch (RemoteException ex) { }
}
}
+
@Override
- public void enterDesktop(int displayId) {
+ public void moveFocusedTaskToDesktop(int displayId) {
IStatusBar bar = mBar;
if (bar != null) {
try {
- bar.enterDesktop(displayId);
+ bar.moveFocusedTaskToDesktop(displayId);
} catch (RemoteException ex) { }
}
}
+
@Override
public void showMediaOutputSwitcher(String packageName) {
IStatusBar bar = mBar;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 60848a7..0effa6c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -795,6 +795,8 @@
Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE);
private final Uri mImmersiveModeConfirmationsUri =
Settings.Secure.getUriFor(Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS);
+ private final Uri mDisableSecureWindowsUri =
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS);
private final Uri mPolicyControlUri =
Settings.Global.getUriFor(Settings.Global.POLICY_CONTROL);
private final Uri mForceDesktopModeOnExternalDisplaysUri = Settings.Global.getUriFor(
@@ -823,6 +825,8 @@
UserHandle.USER_ALL);
resolver.registerContentObserver(mImmersiveModeConfirmationsUri, false, this,
UserHandle.USER_ALL);
+ resolver.registerContentObserver(mDisableSecureWindowsUri, false, this,
+ UserHandle.USER_ALL);
resolver.registerContentObserver(mPolicyControlUri, false, this, UserHandle.USER_ALL);
resolver.registerContentObserver(mForceDesktopModeOnExternalDisplaysUri, false, this,
UserHandle.USER_ALL);
@@ -877,6 +881,11 @@
return;
}
+ if (mDisableSecureWindowsUri.equals(uri)) {
+ updateDisableSecureWindows();
+ return;
+ }
+
@UpdateAnimationScaleMode
final int mode;
if (mWindowAnimationScaleUri.equals(uri)) {
@@ -896,6 +905,7 @@
void loadSettings() {
updateSystemUiSettings(false /* handleChange */);
updateMaximumObscuringOpacityForTouch();
+ updateDisableSecureWindows();
}
void updateMaximumObscuringOpacityForTouch() {
@@ -978,6 +988,28 @@
});
}
}
+
+ void updateDisableSecureWindows() {
+ if (!SystemProperties.getBoolean(SYSTEM_DEBUGGABLE, false)) {
+ return;
+ }
+
+ final boolean disableSecureWindows;
+ try {
+ disableSecureWindows = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.DISABLE_SECURE_WINDOWS, 0) != 0;
+ } catch (Settings.SettingNotFoundException e) {
+ return;
+ }
+ if (mDisableSecureWindows == disableSecureWindows) {
+ return;
+ }
+
+ synchronized (mGlobalLock) {
+ mDisableSecureWindows = disableSecureWindows;
+ mRoot.refreshSecureSurfaceState();
+ }
+ }
}
PowerManager mPowerManager;
@@ -1116,6 +1148,8 @@
private final ScreenRecordingCallbackController mScreenRecordingCallbackController;
+ private volatile boolean mDisableSecureWindows = false;
+
public static WindowManagerService main(final Context context, final InputManagerService im,
final boolean showBootMsgs, WindowManagerPolicy policy,
ActivityTaskManagerService atm) {
@@ -6905,6 +6939,7 @@
pw.print(mLastFinishedFreezeSource);
}
pw.println();
+ pw.print(" mDisableSecureWindows="); pw.println(mDisableSecureWindows);
mInputManagerCallback.dump(pw, " ");
mSnapshotController.dump(pw, " ");
@@ -10076,4 +10111,8 @@
mDragDropController.setGlobalDragListener(listener);
}
}
+
+ boolean getDisableSecureWindows() {
+ return mDisableSecureWindows;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c0cf97d..ca8f790 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1898,6 +1898,10 @@
}
boolean isSecureLocked() {
+ if (mWmService.getDisableSecureWindows()) {
+ return false;
+ }
+
if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
return true;
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 173cb36..cac42b1 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -112,7 +112,8 @@
Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
/*defaultProviderId=*/flattenedPrimaryProviders,
/*isShowAllOptionsRequested=*/ false),
- providerDataList);
+ providerDataList,
+ mRequestSessionMetric);
mClientCallback.onPendingIntent(mPendingIntent);
} catch (RemoteException e) {
mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index f5e1e41..24f6697 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -25,6 +25,7 @@
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.selection.DisabledProviderData;
+import android.credentials.selection.IntentCreationResult;
import android.credentials.selection.IntentFactory;
import android.credentials.selection.ProviderData;
import android.credentials.selection.RequestInfo;
@@ -37,6 +38,8 @@
import android.os.UserHandle;
import android.service.credentials.CredentialProviderInfoFactory;
+import com.android.server.credentials.metrics.RequestSessionMetric;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -159,7 +162,8 @@
* @param providerDataList the list of provider data from remote providers
*/
public PendingIntent createPendingIntent(
- RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
+ RequestInfo requestInfo, ArrayList<ProviderData> providerDataList,
+ RequestSessionMetric requestSessionMetric) {
List<CredentialProviderInfo> allProviders =
CredentialProviderInfoFactory.getCredentialProviderServices(
mContext,
@@ -174,10 +178,12 @@
.map(disabledProvider -> new DisabledProviderData(
disabledProvider.getComponentName().flattenToString())).toList();
- Intent intent;
- intent = IntentFactory.createCredentialSelectorIntent(
- mContext, requestInfo, providerDataList,
- new ArrayList<>(disabledProviderDataList), mResultReceiver);
+ IntentCreationResult intentCreationResult = IntentFactory
+ .createCredentialSelectorIntentForCredMan(mContext, requestInfo, providerDataList,
+ new ArrayList<>(disabledProviderDataList), mResultReceiver);
+ requestSessionMetric.collectUiConfigurationResults(
+ mContext, intentCreationResult, mUserId);
+ Intent intent = intentCreationResult.getIntent();
intent.setAction(UUID.randomUUID().toString());
//TODO: Create unique pending intent using request code and cancel any pre-existing pending
// intents
@@ -197,10 +203,15 @@
* of the pinned entry.
*
* @param requestInfo the information about the request
+ * @param requestSessionMetric the metric object for logging
*/
- public Intent createIntentForAutofill(RequestInfo requestInfo) {
- return IntentFactory.createCredentialSelectorIntentForAutofill(
- mContext, requestInfo, new ArrayList<>(),
- mResultReceiver);
+ public Intent createIntentForAutofill(RequestInfo requestInfo,
+ RequestSessionMetric requestSessionMetric) {
+ IntentCreationResult intentCreationResult = IntentFactory
+ .createCredentialSelectorIntentForAutofill(mContext, requestInfo, new ArrayList<>(),
+ mResultReceiver);
+ requestSessionMetric.collectUiConfigurationResults(
+ mContext, intentCreationResult, mUserId);
+ return intentCreationResult.getIntent();
}
}
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index eff53de..fd2a9a2 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -122,7 +122,8 @@
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
- /*isShowAllOptionsRequested=*/ true));
+ /*isShowAllOptionsRequested=*/ true),
+ mRequestSessionMetric);
List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
for (ProviderData providerData : providerDataList) {
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 6513ae1a..d55d8ef 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -111,7 +111,8 @@
Manifest.permission
.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
/*isShowAllOptionsRequested=*/ false),
- providerDataList);
+ providerDataList,
+ mRequestSessionMetric);
mClientCallback.onPendingIntent(mPendingIntent);
} catch (RemoteException e) {
mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index bdea4f9..16bf1778 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -16,6 +16,7 @@
package com.android.server.credentials;
+import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -68,17 +69,27 @@
*
* @return the uid of a given package
*/
- protected static int getPackageUid(Context context, ComponentName componentName) {
- int sessUid = -1;
- try {
- // Only for T and above, which is fine for our use case
- sessUid = context.getPackageManager().getApplicationInfo(
- componentName.getPackageName(),
- PackageManager.ApplicationInfoFlags.of(0)).uid;
- } catch (Throwable t) {
- Slog.i(TAG, "Couldn't find required uid");
+ protected static int getPackageUid(Context context, ComponentName componentName,
+ @UserIdInt int userId) {
+ if (componentName == null) {
+ return -1;
}
- return sessUid;
+ return getPackageUid(context, componentName.getPackageName(), userId);
+ }
+
+ /** Returns the package uid, or -1 if not found. */
+ public static int getPackageUid(Context context, String packageName,
+ @UserIdInt int userId) {
+ if (packageName == null) {
+ return -1;
+ }
+ try {
+ return context.getPackageManager().getPackageUidAsUser(packageName,
+ PackageManager.PackageInfoFlags.of(0), userId);
+ } catch (Throwable t) {
+ Slog.i(TAG, "Couldn't find uid for " + packageName + ": " + t);
+ return -1;
+ }
}
/**
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index 6e8f7c8..e4b5c77 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -193,7 +193,8 @@
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
/*isShowAllOptionsRequested=*/ false),
- providerDataList);
+ providerDataList,
+ mRequestSessionMetric);
} else {
return null;
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index c16e232..dfc08f0 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -153,7 +153,7 @@
mUserId = userId;
mComponentName = componentName;
mRemoteCredentialService = remoteCredentialService;
- mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName);
+ mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName, userId);
mProviderSessionMetric = new ProviderSessionMetric(
((RequestSession) mCallbacks).mRequestSessionMetric.getSessionIdTrackTwo());
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java b/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
index 2fd3a86..80ce354 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
@@ -22,7 +22,12 @@
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_FOUND;
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_ENABLED;
+import android.credentials.selection.IntentCreationResult;
+/**
+ * Result of attempting to use the config_oemCredentialManagerDialogComponent as the Credential
+ * Manager UI.
+ */
public enum OemUiUsageStatus {
UNKNOWN(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_UNKNOWN),
SUCCESS(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SUCCESS),
@@ -39,4 +44,21 @@
public int getLoggingInt() {
return mLoggingInt;
}
+
+ /** Factory method. */
+ public static OemUiUsageStatus createFrom(IntentCreationResult.OemUiUsageStatus from) {
+ switch (from) {
+ case UNKNOWN:
+ return OemUiUsageStatus.UNKNOWN;
+ case SUCCESS:
+ return OemUiUsageStatus.SUCCESS;
+ case OEM_UI_CONFIG_NOT_SPECIFIED:
+ return OemUiUsageStatus.FAILURE_NOT_SPECIFIED;
+ case OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND:
+ return OemUiUsageStatus.FAILURE_SPECIFIED_BUT_NOT_FOUND;
+ case OEM_UI_CONFIG_SPECIFIED_FOUND_BUT_NOT_ENABLED:
+ return OemUiUsageStatus.FAILURE_SPECIFIED_BUT_NOT_ENABLED;
+ }
+ return OemUiUsageStatus.UNKNOWN;
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
index a77bd3e..619a568 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -30,9 +30,12 @@
import static com.android.server.credentials.metrics.ApiName.GET_CREDENTIAL_VIA_REGISTRY;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.content.ComponentName;
+import android.content.Context;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
+import android.credentials.selection.IntentCreationResult;
import android.credentials.selection.UserSelectionDialogResult;
import android.util.Slog;
@@ -270,6 +273,21 @@
}
}
+ /** Log results of the device Credential Manager UI configuration. */
+ public void collectUiConfigurationResults(Context context, IntentCreationResult result,
+ @UserIdInt int userId) {
+ try {
+ mChosenProviderFinalPhaseMetric.setOemUiUid(MetricUtilities.getPackageUid(
+ context, result.getOemUiPackageName(), userId));
+ mChosenProviderFinalPhaseMetric.setFallbackUiUid(MetricUtilities.getPackageUid(
+ context, result.getFallbackUiPackageName(), userId));
+ mChosenProviderFinalPhaseMetric.setOemUiUsageStatus(
+ OemUiUsageStatus.createFrom(result.getOemUiUsageStatus()));
+ } catch (Exception e) {
+ Slog.w(TAG, "Unexpected error during ui configuration result collection: " + e);
+ }
+ }
+
/**
* Allows encapsulating the overall final phase metric status from the chosen and final
* provider.
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index b9c5b36..b4cf799 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -203,6 +203,7 @@
.thenReturn(new int[] {0});
when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[] {0});
when(mMockActivityManagerInternal.isSystemReady()).thenReturn(true);
+ when(mMockActivityManagerInternal.getCurrentUserId()).thenReturn(mCallingUserId);
when(mMockPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt()))
.thenReturn(Binder.getCallingUid());
when(mMockPackageManagerInternal.isSameApp(anyString(), anyLong(), anyInt(), anyInt()))
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index c30ac2d..682569f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -26,6 +26,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.RescueParty.LEVEL_FACTORY_RESET;
+import static com.android.server.RescueParty.RESCUE_LEVEL_FACTORY_RESET;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -41,9 +42,11 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
+import android.crashrecovery.flags.Flags;
import android.os.RecoverySystem;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.ArraySet;
@@ -55,6 +58,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
@@ -69,6 +73,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -100,6 +105,9 @@
private static final int THROTTLING_DURATION_MIN = 10;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private MockitoSession mSession;
private HashMap<String, String> mSystemSettingsMap;
private HashMap<String, String> mCrashRecoveryPropertiesMap;
@@ -267,6 +275,42 @@
}
@Test
+ public void testBootLoopDetectionWithExecutionForAllRescueLevelsRecoverabilityDetection() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ RescueParty.onSettingsProviderPublished(mMockContext);
+ verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
+ any(Executor.class),
+ mMonitorCallbackCaptor.capture()));
+ HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
+
+ // Record DeviceConfig accesses
+ DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
+ monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
+ monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
+
+ final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
+
+ noteBoot(1);
+ verifyDeviceConfigReset(expectedAllResetNamespaces, verifiedTimesMap);
+
+ noteBoot(2);
+ assertTrue(RescueParty.isRebootPropertySet());
+
+ noteBoot(3);
+ verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
+
+ noteBoot(4);
+ verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_CHANGES);
+
+ noteBoot(5);
+ verifyOnlySettingsReset(Settings.RESET_MODE_TRUSTED_DEFAULTS);
+
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteBoot(6);
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
noteAppCrash(1, true);
@@ -292,6 +336,47 @@
}
@Test
+ public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevelsRecoverability() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ RescueParty.onSettingsProviderPublished(mMockContext);
+ verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
+ any(Executor.class),
+ mMonitorCallbackCaptor.capture()));
+ HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
+
+ // Record DeviceConfig accesses
+ DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
+ monitorCallback.onDeviceConfigAccess(PERSISTENT_PACKAGE, NAMESPACE1);
+ monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
+ monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
+
+ final String[] expectedResetNamespaces = new String[]{NAMESPACE1};
+ final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
+
+ noteAppCrash(1, true);
+ verifyDeviceConfigReset(expectedResetNamespaces, verifiedTimesMap);
+
+ noteAppCrash(2, true);
+ verifyDeviceConfigReset(expectedAllResetNamespaces, verifiedTimesMap);
+
+ noteAppCrash(3, true);
+ assertTrue(RescueParty.isRebootPropertySet());
+
+ noteAppCrash(4, true);
+ verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
+
+ noteAppCrash(5, true);
+ verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_CHANGES);
+
+ noteAppCrash(6, true);
+ verifyOnlySettingsReset(Settings.RESET_MODE_TRUSTED_DEFAULTS);
+
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteAppCrash(7, true);
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
public void testNonPersistentAppOnlyPerformsFlagResets() {
noteAppCrash(1, false);
@@ -316,6 +401,45 @@
}
@Test
+ public void testNonPersistentAppOnlyPerformsFlagResetsRecoverabilityDetection() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ RescueParty.onSettingsProviderPublished(mMockContext);
+ verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
+ any(Executor.class),
+ mMonitorCallbackCaptor.capture()));
+ HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
+
+ // Record DeviceConfig accesses
+ DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
+ monitorCallback.onDeviceConfigAccess(NON_PERSISTENT_PACKAGE, NAMESPACE1);
+ monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
+ monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
+
+ final String[] expectedResetNamespaces = new String[]{NAMESPACE1};
+ final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
+
+ noteAppCrash(1, false);
+ verifyDeviceConfigReset(expectedResetNamespaces, verifiedTimesMap);
+
+ noteAppCrash(2, false);
+ verifyDeviceConfigReset(expectedAllResetNamespaces, verifiedTimesMap);
+
+ noteAppCrash(3, false);
+ assertFalse(RescueParty.isRebootPropertySet());
+
+ noteAppCrash(4, false);
+ verifyNoSettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
+ noteAppCrash(5, false);
+ verifyNoSettingsReset(Settings.RESET_MODE_UNTRUSTED_CHANGES);
+ noteAppCrash(6, false);
+ verifyNoSettingsReset(Settings.RESET_MODE_TRUSTED_DEFAULTS);
+
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteAppCrash(7, false);
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
public void testNonPersistentAppCrashDetectionWithScopedResets() {
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
@@ -451,6 +575,19 @@
}
@Test
+ public void testIsRecoveryTriggeredRebootRecoverabilityDetection() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ for (int i = 0; i < RESCUE_LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i + 1);
+ }
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteBoot(RESCUE_LEVEL_FACTORY_RESET + 1);
+ assertTrue(RescueParty.isRecoveryTriggeredReboot());
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
public void testIsRecoveryTriggeredRebootOnlyAfterRebootCompleted() {
for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
noteBoot(i + 1);
@@ -469,6 +606,25 @@
}
@Test
+ public void testIsRecoveryTriggeredRebootOnlyAfterRebootCompletedRecoverabilityDetection() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ for (int i = 0; i < RESCUE_LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i + 1);
+ }
+ int mitigationCount = RESCUE_LEVEL_FACTORY_RESET + 1;
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ noteBoot(mitigationCount++);
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ noteBoot(mitigationCount++);
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ noteBoot(mitigationCount++);
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteBoot(mitigationCount + 1);
+ assertTrue(RescueParty.isRecoveryTriggeredReboot());
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
public void testThrottlingOnBootFailures() {
setCrashRecoveryPropAttemptingReboot(false);
long now = System.currentTimeMillis();
@@ -481,6 +637,19 @@
}
@Test
+ public void testThrottlingOnBootFailuresRecoverabilityDetection() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ setCrashRecoveryPropAttemptingReboot(false);
+ long now = System.currentTimeMillis();
+ long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1);
+ setCrashRecoveryPropLastFactoryReset(beforeTimeout);
+ for (int i = 1; i <= RESCUE_LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i);
+ }
+ assertFalse(RescueParty.isRecoveryTriggeredReboot());
+ }
+
+ @Test
public void testThrottlingOnAppCrash() {
setCrashRecoveryPropAttemptingReboot(false);
long now = System.currentTimeMillis();
@@ -493,6 +662,19 @@
}
@Test
+ public void testThrottlingOnAppCrashRecoverabilityDetection() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ setCrashRecoveryPropAttemptingReboot(false);
+ long now = System.currentTimeMillis();
+ long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1);
+ setCrashRecoveryPropLastFactoryReset(beforeTimeout);
+ for (int i = 0; i <= RESCUE_LEVEL_FACTORY_RESET; i++) {
+ noteAppCrash(i + 1, true);
+ }
+ assertFalse(RescueParty.isRecoveryTriggeredReboot());
+ }
+
+ @Test
public void testNotThrottlingAfterTimeoutOnBootFailures() {
setCrashRecoveryPropAttemptingReboot(false);
long now = System.currentTimeMillis();
@@ -503,6 +685,20 @@
}
assertTrue(RescueParty.isRecoveryTriggeredReboot());
}
+
+ @Test
+ public void testNotThrottlingAfterTimeoutOnBootFailuresRecoverabilityDetection() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ setCrashRecoveryPropAttemptingReboot(false);
+ long now = System.currentTimeMillis();
+ long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1);
+ setCrashRecoveryPropLastFactoryReset(afterTimeout);
+ for (int i = 1; i <= RESCUE_LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i);
+ }
+ assertTrue(RescueParty.isRecoveryTriggeredReboot());
+ }
+
@Test
public void testNotThrottlingAfterTimeoutOnAppCrash() {
setCrashRecoveryPropAttemptingReboot(false);
@@ -516,6 +712,19 @@
}
@Test
+ public void testNotThrottlingAfterTimeoutOnAppCrashRecoverabilityDetection() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ setCrashRecoveryPropAttemptingReboot(false);
+ long now = System.currentTimeMillis();
+ long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1);
+ setCrashRecoveryPropLastFactoryReset(afterTimeout);
+ for (int i = 0; i <= RESCUE_LEVEL_FACTORY_RESET; i++) {
+ noteAppCrash(i + 1, true);
+ }
+ assertTrue(RescueParty.isRecoveryTriggeredReboot());
+ }
+
+ @Test
public void testNativeRescuePartyResets() {
doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed());
doReturn(FAKE_RESET_NATIVE_NAMESPACES).when(
@@ -531,6 +740,7 @@
@Test
public void testExplicitlyEnablingAndDisablingRescue() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
@@ -543,6 +753,7 @@
@Test
public void testDisablingRescueByDeviceConfigFlag() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
@@ -568,6 +779,20 @@
}
@Test
+ public void testDisablingFactoryResetByDeviceConfigFlagRecoverabilityDetection() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, Boolean.toString(true));
+
+ for (int i = 0; i < RESCUE_LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i + 1);
+ }
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+
+ // Restore the property value initialized in SetUp()
+ SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, "");
+ }
+
+ @Test
public void testHealthCheckLevels() {
RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
@@ -594,6 +819,46 @@
}
@Test
+ public void testHealthCheckLevelsRecoverabilityDetection() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+
+ // Ensure that no action is taken for cases where the failure reason is unknown
+ assertEquals(observer.onHealthCheckFailed(sFailingPackage,
+ PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
+
+ // Ensure the correct user impact is returned for each mitigation count.
+ assertEquals(observer.onHealthCheckFailed(sFailingPackage,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
+
+ assertEquals(observer.onHealthCheckFailed(sFailingPackage,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_20);
+
+ assertEquals(observer.onHealthCheckFailed(sFailingPackage,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_20);
+
+ assertEquals(observer.onHealthCheckFailed(sFailingPackage,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_20);
+
+ assertEquals(observer.onHealthCheckFailed(sFailingPackage,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_20);
+
+ assertEquals(observer.onHealthCheckFailed(sFailingPackage,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 6),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_20);
+
+ assertEquals(observer.onHealthCheckFailed(sFailingPackage,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 7),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_20);
+ }
+
+ @Test
public void testBootLoopLevels() {
RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
@@ -606,6 +871,19 @@
}
@Test
+ public void testBootLoopLevelsRecoverabilityDetection() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+
+ assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_20);
+ assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+ assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_71);
+ assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_75);
+ assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_80);
+ assertEquals(observer.onBootLoop(6), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
+ }
+
+ @Test
public void testResetDeviceConfigForPackagesOnlyRuntimeMap() {
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
@@ -727,11 +1005,26 @@
private void verifySettingsResets(int resetMode, String[] resetNamespaces,
HashMap<String, Integer> configResetVerifiedTimesMap) {
+ verifyOnlySettingsReset(resetMode);
+ verifyDeviceConfigReset(resetNamespaces, configResetVerifiedTimesMap);
+ }
+
+ private void verifyOnlySettingsReset(int resetMode) {
verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
resetMode, UserHandle.USER_SYSTEM));
verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(),
eq(resetMode), anyInt()));
- // Verify DeviceConfig resets
+ }
+
+ private void verifyNoSettingsReset(int resetMode) {
+ verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
+ resetMode, UserHandle.USER_SYSTEM), never());
+ verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(),
+ eq(resetMode), anyInt()), never());
+ }
+
+ private void verifyDeviceConfigReset(String[] resetNamespaces,
+ Map<String, Integer> configResetVerifiedTimesMap) {
if (resetNamespaces == null) {
verify(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()), never());
} else {
@@ -818,9 +1111,16 @@
// mock properties in BootThreshold
try {
- mSpyBootThreshold = spy(watchdog.new BootThreshold(
- PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
- PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS));
+ if (Flags.recoverabilityDetection()) {
+ mSpyBootThreshold = spy(watchdog.new BootThreshold(
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT));
+ } else {
+ mSpyBootThreshold = spy(watchdog.new BootThreshold(
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS));
+ }
mCrashRecoveryPropertiesMap = new HashMap<>();
doAnswer((Answer<Integer>) invocationOnMock -> {
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 420af86..1b2c0e4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -41,6 +41,7 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
@@ -57,6 +58,7 @@
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.app.ApplicationExitInfo;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
@@ -239,6 +241,7 @@
mConstants.TIMEOUT = 200;
mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
+ mConstants.MAX_FROZEN_OUTGOING_BROADCASTS = 10;
}
@After
@@ -2368,6 +2371,34 @@
verifyScheduleReceiver(times(1), receiverYellowApp, timeTick);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DEFER_OUTGOING_BROADCASTS)
+ public void testKillProcess_excessiveOutgoingBroadcastsWhileCached() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ setProcessFreezable(callerApp, true /* pendingFreeze */, false /* frozen */);
+ waitForIdle();
+
+ final int count = mConstants.MAX_FROZEN_OUTGOING_BROADCASTS + 1;
+ for (int i = 0; i < count; ++i) {
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK + "_" + i);
+ enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, List.of(
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
+ }
+ // Verify that we invoke the call to freeze the caller app.
+ verify(mAms.mOomAdjuster.mCachedAppOptimizer, atLeastOnce())
+ .freezeAppAsyncImmediateLSP(callerApp);
+
+ // Verify that the caller process is killed
+ assertTrue(callerApp.isKilled());
+ verify(mProcessList).noteAppKill(same(callerApp),
+ eq(ApplicationExitInfo.REASON_OTHER),
+ eq(ApplicationExitInfo.SUBREASON_EXCESSIVE_OUTGOING_BROADCASTS_WHILE_CACHED),
+ any(String.class));
+
+ waitForIdle();
+ assertNull(mAms.getProcessRecordLocked(PACKAGE_BLUE, getUidForPackage(PACKAGE_BLUE)));
+ }
+
private long getReceiverScheduledTime(@NonNull BroadcastRecord r, @NonNull Object receiver) {
for (int i = 0; i < r.receivers.size(); ++i) {
if (isReceiverEquals(receiver, r.receivers.get(i))) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 53c460c..9d32ed8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -21,7 +21,6 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
-import static android.view.accessibility.Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG;
import static android.view.accessibility.Flags.FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
@@ -885,7 +884,6 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void testIsAccessibilityServiceWarningRequired_requiredByDefault() {
mockManageAccessibilityGranted(mTestableContext);
final AccessibilityServiceInfo info = mockAccessibilityServiceInfo(COMPONENT_NAME);
@@ -894,7 +892,6 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void testIsAccessibilityServiceWarningRequired_notRequiredIfAlreadyEnabled() {
mockManageAccessibilityGranted(mTestableContext);
final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(COMPONENT_NAME);
@@ -909,7 +906,6 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void testIsAccessibilityServiceWarningRequired_notRequiredIfExistingShortcut() {
mockManageAccessibilityGranted(mTestableContext);
final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
@@ -930,9 +926,7 @@
}
@Test
- @RequiresFlagsEnabled({
- FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG,
- FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES})
+ @RequiresFlagsEnabled(FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES)
public void testIsAccessibilityServiceWarningRequired_notRequiredIfAllowlisted() {
mockManageAccessibilityGranted(mTestableContext);
final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index 6e8d6dc..f44879f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -470,6 +470,27 @@
}
@Test
+ public void onWindowsChanged_shouldNotReportfullyOccludedWindow() {
+ final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(frontWindow, new Region(100, 100, 300, 300));
+ final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
+
+ // index 1 is focused. Let's use the next one for this test.
+ final AccessibilityWindow occludedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(2);
+ setRegionForMockAccessibilityWindow(occludedWindow, new Region(150, 150, 250, 250));
+ final int occludedWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, occludedWindow.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasItem(windowId(frontWindowId)));
+ assertThat(a11yWindows, not(hasItem(windowId(occludedWindowId))));
+ }
+
+ @Test
public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
assertNotEquals("new title",
toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
index a4628ee..4d1d17f 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
@@ -141,6 +141,7 @@
@Test
public void virtualDevice_hasCustomAudioInputSupport() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+ mSetFlagsRule.enableFlags(android.media.audiopolicy.Flags.FLAG_AUDIO_MIX_TEST_API);
VirtualDevice virtualDevice =
new VirtualDevice(
@@ -150,6 +151,10 @@
assertThat(virtualDevice.hasCustomAudioInputSupport()).isFalse();
when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO)).thenReturn(DEVICE_POLICY_CUSTOM);
+ when(mVirtualDevice.hasCustomAudioInputSupport()).thenReturn(false);
+ assertThat(virtualDevice.hasCustomAudioInputSupport()).isFalse();
+
+ when(mVirtualDevice.hasCustomAudioInputSupport()).thenReturn(true);
assertThat(virtualDevice.hasCustomAudioInputSupport()).isTrue();
}
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
index caaee63..4d48276 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
@@ -30,10 +30,12 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+@Ignore // b/330376055: Write tests for functionality for both dVRR and MRR devices.
@RunWith(AndroidJUnit4.class)
public class SurfaceControlTest {
private static final String TAG = "SurfaceControlTest";
diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp
index e0e6c4c..2c5fdd3 100644
--- a/tests/PackageWatchdog/Android.bp
+++ b/tests/PackageWatchdog/Android.bp
@@ -28,8 +28,10 @@
static_libs: [
"junit",
"mockito-target-extended-minus-junit4",
+ "flag-junit",
"frameworks-base-testutils",
"androidx.test.rules",
+ "PlatformProperties",
"services.core",
"services.net",
"truth",
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
new file mode 100644
index 0000000..081da11
--- /dev/null
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -0,0 +1,644 @@
+/*
+ * 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.server;
+
+import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+import android.crashrecovery.flags.Flags;
+import android.net.ConnectivityModuleConnector;
+import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener;
+import android.os.Handler;
+import android.os.SystemProperties;
+import android.os.test.TestLooper;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.DeviceConfig;
+import android.util.AtomicFile;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.RescueParty.RescuePartyObserver;
+import com.android.server.pm.ApexManager;
+import com.android.server.rollback.RollbackPackageHealthObserver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+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;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Test CrashRecovery, integration tests that include PackageWatchdog, RescueParty and
+ * RollbackPackageHealthObserver
+ */
+public class CrashRecoveryTest {
+ private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
+ "persist.device_config.configuration.disable_rescue_party";
+
+ private static final String APP_A = "com.package.a";
+ private static final String APP_B = "com.package.b";
+ private static final String APP_C = "com.package.c";
+ private static final long VERSION_CODE = 1L;
+ private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
+
+ private static final RollbackInfo ROLLBACK_INFO_LOW = getRollbackInfo(APP_A, VERSION_CODE, 1,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ private static final RollbackInfo ROLLBACK_INFO_HIGH = getRollbackInfo(APP_B, VERSION_CODE, 2,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ private static final RollbackInfo ROLLBACK_INFO_MANUAL = getRollbackInfo(APP_C, VERSION_CODE, 3,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private final TestClock mTestClock = new TestClock();
+ private TestLooper mTestLooper;
+ private Context mSpyContext;
+ // Keep track of all created watchdogs to apply device config changes
+ private List<PackageWatchdog> mAllocatedWatchdogs;
+ @Mock
+ private ConnectivityModuleConnector mConnectivityModuleConnector;
+ @Mock
+ private PackageManager mMockPackageManager;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private ApexManager mApexManager;
+ @Mock
+ RollbackManager mRollbackManager;
+ // Mock only sysprop apis
+ private PackageWatchdog.BootThreshold mSpyBootThreshold;
+ @Captor
+ private ArgumentCaptor<ConnectivityModuleHealthListener> mConnectivityModuleCallbackCaptor;
+ private MockitoSession mSession;
+ private HashMap<String, String> mSystemSettingsMap;
+ private HashMap<String, String> mCrashRecoveryPropertiesMap;
+
+ @Before
+ public void setUp() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ MockitoAnnotations.initMocks(this);
+ new File(InstrumentationRegistry.getContext().getFilesDir(),
+ "package-watchdog.xml").delete();
+ adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG,
+ Manifest.permission.WRITE_DEVICE_CONFIG);
+ mTestLooper = new TestLooper();
+ mSpyContext = spy(InstrumentationRegistry.getContext());
+ when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
+ final PackageInfo res = new PackageInfo();
+ res.packageName = inv.getArgument(0);
+ res.setLongVersionCode(VERSION_CODE);
+ return res;
+ });
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
+ .spyStatic(RescueParty.class)
+ .startMocking();
+ mSystemSettingsMap = new HashMap<>();
+
+ // Mock SystemProperties setter and various getters
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ String value = invocationOnMock.getArgument(1);
+
+ mSystemSettingsMap.put(key, value);
+ return null;
+ }
+ ).when(() -> SystemProperties.set(anyString(), anyString()));
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ int defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Integer.parseInt(storedValue);
+ }
+ ).when(() -> SystemProperties.getInt(anyString(), anyInt()));
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ long defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Long.parseLong(storedValue);
+ }
+ ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
+
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ boolean defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Boolean.parseBoolean(storedValue);
+ }
+ ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean()));
+
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
+ Boolean.toString(true), false);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+ Integer.toString(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT), false);
+
+ mAllocatedWatchdogs = new ArrayList<>();
+ RescuePartyObserver.reset();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ dropShellPermissions();
+ mSession.finishMocking();
+ // Clean up listeners since too many listeners will delay notifications significantly
+ for (PackageWatchdog watchdog : mAllocatedWatchdogs) {
+ watchdog.removePropertyChangedListener();
+ }
+ mAllocatedWatchdogs.clear();
+ }
+
+ @Test
+ public void testBootLoopWithRescueParty() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(1);
+ int bootCounter = 0;
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
+ watchdog.noteBoot();
+ bootCounter += 1;
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(1);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
+
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
+ watchdog.noteBoot();
+ bootCounter += 1;
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(2);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
+
+ int bootLoopThreshold = PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - bootCounter;
+ for (int i = 0; i < bootLoopThreshold; i++) {
+ watchdog.noteBoot();
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(3);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(4);
+
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
+ watchdog.noteBoot();
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(4);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(5);
+
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
+ watchdog.noteBoot();
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(5);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(6);
+
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
+ watchdog.noteBoot();
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(6);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(7);
+ }
+
+ @Test
+ public void testBootLoopWithRollbackPackageHealthObserver() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ RollbackPackageHealthObserver rollbackObserver =
+ setUpRollbackPackageHealthObserver(watchdog);
+
+ verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+ int bootCounter = 0;
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
+ watchdog.noteBoot();
+ bootCounter += 1;
+ }
+ verify(rollbackObserver).executeBootLoopMitigation(1);
+ verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+
+ // Update the list of available rollbacks after executing bootloop mitigation once
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH,
+ ROLLBACK_INFO_MANUAL));
+
+ int bootLoopThreshold = PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - bootCounter;
+ for (int i = 0; i < bootLoopThreshold; i++) {
+ watchdog.noteBoot();
+ }
+ verify(rollbackObserver).executeBootLoopMitigation(2);
+ verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+
+ // Update the list of available rollbacks after executing bootloop mitigation once
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL));
+
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
+ watchdog.noteBoot();
+ }
+ verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+ }
+
+ @Test
+ public void testBootLoopWithRescuePartyAndRollbackPackageHealthObserver() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+ RollbackPackageHealthObserver rollbackObserver =
+ setUpRollbackPackageHealthObserver(watchdog);
+
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(1);
+ verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+ int bootCounter = 0;
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
+ watchdog.noteBoot();
+ bootCounter += 1;
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(1);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
+ verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
+ watchdog.noteBoot();
+ bootCounter += 1;
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(2);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
+ verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
+ watchdog.noteBoot();
+ bootCounter += 1;
+ }
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
+ verify(rollbackObserver).executeBootLoopMitigation(1);
+ verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+ // Update the list of available rollbacks after executing bootloop mitigation once
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH,
+ ROLLBACK_INFO_MANUAL));
+
+ int bootLoopThreshold = PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - bootCounter;
+ for (int i = 0; i < bootLoopThreshold; i++) {
+ watchdog.noteBoot();
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(3);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(4);
+ verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
+ watchdog.noteBoot();
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(4);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(5);
+ verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
+ watchdog.noteBoot();
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(5);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(6);
+ verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
+ watchdog.noteBoot();
+ }
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(6);
+ verify(rollbackObserver).executeBootLoopMitigation(2);
+ verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+ // Update the list of available rollbacks after executing bootloop mitigation
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL));
+
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
+ watchdog.noteBoot();
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(6);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(7);
+ verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+ }
+
+ RollbackPackageHealthObserver setUpRollbackPackageHealthObserver(PackageWatchdog watchdog) {
+ RollbackPackageHealthObserver rollbackObserver =
+ spy(new RollbackPackageHealthObserver(mSpyContext, mApexManager));
+ when(mSpyContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW,
+ ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+ when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
+
+ watchdog.registerHealthObserver(rollbackObserver);
+ return rollbackObserver;
+ }
+
+ RescuePartyObserver setUpRescuePartyObserver(PackageWatchdog watchdog) {
+ setCrashRecoveryPropRescueBootCount(0);
+ RescuePartyObserver rescuePartyObserver = spy(RescuePartyObserver.getInstance(mSpyContext));
+ assertFalse(RescueParty.isRebootPropertySet());
+ watchdog.registerHealthObserver(rescuePartyObserver);
+ return rescuePartyObserver;
+ }
+
+ private static RollbackInfo getRollbackInfo(String packageName, long versionCode,
+ int rollbackId, int rollbackUserImpact) {
+ VersionedPackage appFrom = new VersionedPackage(packageName, versionCode + 1);
+ VersionedPackage appTo = new VersionedPackage(packageName, versionCode);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appFrom, appTo, null,
+ null, false, false, null);
+ RollbackInfo rollbackInfo = new RollbackInfo(rollbackId, List.of(packageRollbackInfo),
+ false, null, 111, rollbackUserImpact);
+ return rollbackInfo;
+ }
+
+ private void adoptShellPermissions(String... permissions) {
+ androidx.test.platform.app.InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity(permissions);
+ }
+
+ private void dropShellPermissions() {
+ androidx.test.platform.app.InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+
+ private PackageWatchdog createWatchdog() {
+ return createWatchdog(new TestController(), true /* withPackagesReady */);
+ }
+
+ private PackageWatchdog createWatchdog(TestController controller, boolean withPackagesReady) {
+ AtomicFile policyFile =
+ new AtomicFile(new File(mSpyContext.getFilesDir(), "package-watchdog.xml"));
+ Handler handler = new Handler(mTestLooper.getLooper());
+ PackageWatchdog watchdog =
+ new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller,
+ mConnectivityModuleConnector, mTestClock);
+ mockCrashRecoveryProperties(watchdog);
+
+ // Verify controller is not automatically started
+ assertThat(controller.mIsEnabled).isFalse();
+ if (withPackagesReady) {
+ // Only capture the NetworkStack callback for the latest registered watchdog
+ reset(mConnectivityModuleConnector);
+ watchdog.onPackagesReady();
+ // Verify controller by default is started when packages are ready
+ assertThat(controller.mIsEnabled).isTrue();
+
+ verify(mConnectivityModuleConnector).registerHealthListener(
+ mConnectivityModuleCallbackCaptor.capture());
+ }
+ mAllocatedWatchdogs.add(watchdog);
+ return watchdog;
+ }
+
+ // Mock CrashRecoveryProperties as they cannot be accessed due to SEPolicy restrictions
+ private void mockCrashRecoveryProperties(PackageWatchdog watchdog) {
+ mCrashRecoveryPropertiesMap = new HashMap<>();
+
+ // mock properties in RescueParty
+ try {
+
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.attempting_factory_reset", "false");
+ return Boolean.parseBoolean(storedValue);
+ }).when(() -> RescueParty.isFactoryResetPropertySet());
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ boolean value = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_factory_reset",
+ Boolean.toString(value));
+ return null;
+ }).when(() -> RescueParty.setFactoryResetProperty(anyBoolean()));
+
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.attempting_reboot", "false");
+ return Boolean.parseBoolean(storedValue);
+ }).when(() -> RescueParty.isRebootPropertySet());
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ boolean value = invocationOnMock.getArgument(0);
+ setCrashRecoveryPropAttemptingReboot(value);
+ return null;
+ }).when(() -> RescueParty.setRebootProperty(anyBoolean()));
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("persist.crashrecovery.last_factory_reset", "0");
+ return Long.parseLong(storedValue);
+ }).when(() -> RescueParty.getLastFactoryResetTimeMs());
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ long value = invocationOnMock.getArgument(0);
+ setCrashRecoveryPropLastFactoryReset(value);
+ return null;
+ }).when(() -> RescueParty.setLastFactoryResetTimeMs(anyLong()));
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.max_rescue_level_attempted", "0");
+ return Integer.parseInt(storedValue);
+ }).when(() -> RescueParty.getMaxRescueLevelAttempted());
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ int value = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.max_rescue_level_attempted",
+ Integer.toString(value));
+ return null;
+ }).when(() -> RescueParty.setMaxRescueLevelAttempted(anyInt()));
+
+ } catch (Exception e) {
+ // tests will fail, just printing the error
+ System.out.println("Error while mocking crashrecovery properties " + e.getMessage());
+ }
+
+ try {
+ if (Flags.recoverabilityDetection()) {
+ mSpyBootThreshold = spy(watchdog.new BootThreshold(
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT));
+ } else {
+ mSpyBootThreshold = spy(watchdog.new BootThreshold(
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS));
+ }
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.rescue_boot_count", "0");
+ return Integer.parseInt(storedValue);
+ }).when(mSpyBootThreshold).getCount();
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ int count = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_count",
+ Integer.toString(count));
+ return null;
+ }).when(mSpyBootThreshold).setCount(anyInt());
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.boot_mitigation_count", "0");
+ return Integer.parseInt(storedValue);
+ }).when(mSpyBootThreshold).getMitigationCount();
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ int count = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_count",
+ Integer.toString(count));
+ return null;
+ }).when(mSpyBootThreshold).setMitigationCount(anyInt());
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.rescue_boot_start", "0");
+ return Long.parseLong(storedValue);
+ }).when(mSpyBootThreshold).getStart();
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ long count = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_start",
+ Long.toString(count));
+ return null;
+ }).when(mSpyBootThreshold).setStart(anyLong());
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.boot_mitigation_start", "0");
+ return Long.parseLong(storedValue);
+ }).when(mSpyBootThreshold).getMitigationStart();
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ long count = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_start",
+ Long.toString(count));
+ return null;
+ }).when(mSpyBootThreshold).setMitigationStart(anyLong());
+
+ Field mBootThresholdField = watchdog.getClass().getDeclaredField("mBootThreshold");
+ mBootThresholdField.setAccessible(true);
+ mBootThresholdField.set(watchdog, mSpyBootThreshold);
+ } catch (Exception e) {
+ // tests will fail, just printing the error
+ System.out.println("Error detected while spying BootThreshold" + e.getMessage());
+ }
+ }
+
+ private void setCrashRecoveryPropRescueBootCount(int count) {
+ mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_count",
+ Integer.toString(count));
+ }
+
+ private void setCrashRecoveryPropAttemptingReboot(boolean value) {
+ mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_reboot",
+ Boolean.toString(value));
+ }
+
+ private void setCrashRecoveryPropLastFactoryReset(long value) {
+ mCrashRecoveryPropertiesMap.put("persist.crashrecovery.last_factory_reset",
+ Long.toString(value));
+ }
+
+ private static class TestController extends ExplicitHealthCheckController {
+ TestController() {
+ super(null /* controller */);
+ }
+
+ private boolean mIsEnabled;
+ private List<String> mSupportedPackages = new ArrayList<>();
+ private List<String> mRequestedPackages = new ArrayList<>();
+ private Consumer<List<PackageConfig>> mSupportedConsumer;
+ private List<Set> mSyncRequests = new ArrayList<>();
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mIsEnabled = enabled;
+ if (!mIsEnabled) {
+ mSupportedPackages.clear();
+ }
+ }
+
+ @Override
+ public void setCallbacks(Consumer<String> passedConsumer,
+ Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) {
+ mSupportedConsumer = supportedConsumer;
+ }
+
+ @Override
+ public void syncRequests(Set<String> packages) {
+ mSyncRequests.add(packages);
+ mRequestedPackages.clear();
+ if (mIsEnabled) {
+ packages.retainAll(mSupportedPackages);
+ mRequestedPackages.addAll(packages);
+ List<PackageConfig> packageConfigs = new ArrayList<>();
+ for (String packageName: packages) {
+ packageConfigs.add(new PackageConfig(packageName, SHORT_DURATION));
+ }
+ mSupportedConsumer.accept(packageConfigs);
+ } else {
+ mSupportedConsumer.accept(Collections.emptyList());
+ }
+ }
+ }
+
+ private static class TestClock implements PackageWatchdog.SystemClock {
+ // Note 0 is special to the internal clock of PackageWatchdog. We need to start from
+ // a non-zero value in order not to disrupt the logic of PackageWatchdog.
+ private long mUpTimeMillis = 1;
+ @Override
+ public long uptimeMillis() {
+ return mUpTimeMillis;
+ }
+ }
+}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 75284c7..4f27e06 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -36,11 +36,13 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
+import android.crashrecovery.flags.Flags;
import android.net.ConnectivityModuleConnector;
import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener;
import android.os.Handler;
import android.os.SystemProperties;
import android.os.test.TestLooper;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
import android.util.Xml;
@@ -54,11 +56,13 @@
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.PackageWatchdog.HealthCheckState;
import com.android.server.PackageWatchdog.MonitoredPackage;
+import com.android.server.PackageWatchdog.ObserverInternal;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
@@ -99,6 +103,10 @@
private static final String OBSERVER_NAME_4 = "observer4";
private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5);
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private final TestClock mTestClock = new TestClock();
private TestLooper mTestLooper;
private Context mSpyContext;
@@ -128,6 +136,7 @@
@Before
public void setUp() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
MockitoAnnotations.initMocks(this);
new File(InstrumentationRegistry.getContext().getFilesDir(),
"package-watchdog.xml").delete();
@@ -444,6 +453,7 @@
*/
@Test
public void testPackageFailureNotifyAllDifferentImpacts() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver observerNone = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
@@ -488,6 +498,52 @@
assertThat(observerLowPackages).containsExactly(APP_A);
}
+ @Test
+ public void testPackageFailureNotifyAllDifferentImpactsRecoverability() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observerNone = new TestObserver(OBSERVER_NAME_1,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
+ TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+ TestObserver observerMid = new TestObserver(OBSERVER_NAME_3,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
+ TestObserver observerLow = new TestObserver(OBSERVER_NAME_4,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
+
+ // Start observing for all impact observers
+ watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+ SHORT_DURATION);
+ watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
+ SHORT_DURATION);
+ watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
+ SHORT_DURATION);
+ watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
+ SHORT_DURATION);
+
+ // Then fail all apps above the threshold
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+ new VersionedPackage(APP_B, VERSION_CODE),
+ new VersionedPackage(APP_C, VERSION_CODE),
+ new VersionedPackage(APP_D, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
+
+ // Verify least impact observers are notifed of package failures
+ List<String> observerNonePackages = observerNone.mMitigatedPackages;
+ List<String> observerHighPackages = observerHigh.mMitigatedPackages;
+ List<String> observerMidPackages = observerMid.mMitigatedPackages;
+ List<String> observerLowPackages = observerLow.mMitigatedPackages;
+
+ // APP_D failure observed by only observerNone is not caught cos its impact is none
+ assertThat(observerNonePackages).isEmpty();
+ // APP_C failure is caught by observerHigh cos it's the lowest impact observer
+ assertThat(observerHighPackages).containsExactly(APP_C);
+ // APP_B failure is caught by observerMid cos it's the lowest impact observer
+ assertThat(observerMidPackages).containsExactly(APP_B);
+ // APP_A failure is caught by observerLow cos it's the lowest impact observer
+ assertThat(observerLowPackages).containsExactly(APP_A);
+ }
+
/**
* Test package failure and least impact observers are notified successively.
* State transistions:
@@ -501,6 +557,7 @@
*/
@Test
public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
@@ -563,11 +620,76 @@
assertThat(observerSecond.mMitigatedPackages).isEmpty();
}
+ @Test
+ public void testPackageFailureNotifyLeastImpactSuccessivelyRecoverability() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
+ TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
+
+ // Start observing for observerFirst and observerSecond with failure handling
+ watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+
+ // Then fail APP_A above the threshold
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
+
+ // Verify only observerFirst is notifed
+ assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
+ assertThat(observerSecond.mMitigatedPackages).isEmpty();
+
+ // After observerFirst handles failure, next action it has is high impact
+ observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
+ observerFirst.mMitigatedPackages.clear();
+ observerSecond.mMitigatedPackages.clear();
+
+ // Then fail APP_A again above the threshold
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
+
+ // Verify only observerSecond is notifed cos it has least impact
+ assertThat(observerSecond.mMitigatedPackages).containsExactly(APP_A);
+ assertThat(observerFirst.mMitigatedPackages).isEmpty();
+
+ // After observerSecond handles failure, it has no further actions
+ observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ observerFirst.mMitigatedPackages.clear();
+ observerSecond.mMitigatedPackages.clear();
+
+ // Then fail APP_A again above the threshold
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
+
+ // Verify only observerFirst is notifed cos it has the only action
+ assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
+ assertThat(observerSecond.mMitigatedPackages).isEmpty();
+
+ // After observerFirst handles failure, it too has no further actions
+ observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ observerFirst.mMitigatedPackages.clear();
+ observerSecond.mMitigatedPackages.clear();
+
+ // Then fail APP_A again above the threshold
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
+
+ // Verify no observer is notified cos no actions left
+ assertThat(observerFirst.mMitigatedPackages).isEmpty();
+ assertThat(observerSecond.mMitigatedPackages).isEmpty();
+ }
+
/**
* Test package failure and notifies only one observer even with observer impact tie.
*/
@Test
public void testPackageFailureNotifyOneSameImpact() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
@@ -588,6 +710,28 @@
assertThat(observer2.mMitigatedPackages).isEmpty();
}
+ @Test
+ public void testPackageFailureNotifyOneSameImpactRecoverabilityDetection() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+
+ // Start observing for observer1 and observer2 with failure handling
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+
+ // Then fail APP_A above the threshold
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
+
+ // Verify only one observer is notifed
+ assertThat(observer1.mMitigatedPackages).containsExactly(APP_A);
+ assertThat(observer2.mMitigatedPackages).isEmpty();
+ }
+
/**
* Test package passing explicit health checks does not fail and vice versa.
*/
@@ -818,6 +962,7 @@
@Test
public void testNetworkStackFailure() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
final PackageWatchdog wd = createWatchdog();
// Start observing with failure handling
@@ -835,6 +980,25 @@
assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
}
+ @Test
+ public void testNetworkStackFailureRecoverabilityDetection() {
+ final PackageWatchdog wd = createWatchdog();
+
+ // Start observing with failure handling
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
+ wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+
+ // Notify of NetworkStack failure
+ mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
+
+ // Run handler so package failures are dispatched to observers
+ mTestLooper.dispatchAll();
+
+ // Verify the NetworkStack observer is notified
+ assertThat(observer.mMitigatedPackages).isEmpty();
+ }
+
/** Test default values are used when device property is invalid. */
@Test
public void testInvalidConfig_watchdogTriggerFailureCount() {
@@ -1045,6 +1209,7 @@
/** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */
@Test
public void testBootLoopDetection_meetsThreshold() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
watchdog.registerHealthObserver(bootObserver);
@@ -1054,6 +1219,16 @@
assertThat(bootObserver.mitigatedBootLoop()).isTrue();
}
+ @Test
+ public void testBootLoopDetection_meetsThresholdRecoverability() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
+ watchdog.registerHealthObserver(bootObserver);
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD; i++) {
+ watchdog.noteBoot();
+ }
+ assertThat(bootObserver.mitigatedBootLoop()).isTrue();
+ }
/**
* Ensure that boot loop mitigation is not done when the number of boots does not meet the
@@ -1071,10 +1246,43 @@
}
/**
+ * Ensure that boot loop mitigation is not done when the number of boots does not meet the
+ * threshold.
+ */
+ @Test
+ public void testBootLoopDetection_doesNotMeetThresholdRecoverabilityLowImpact() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
+ watchdog.registerHealthObserver(bootObserver);
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
+ watchdog.noteBoot();
+ }
+ assertThat(bootObserver.mitigatedBootLoop()).isFalse();
+ }
+
+ /**
+ * Ensure that boot loop mitigation is not done when the number of boots does not meet the
+ * threshold.
+ */
+ @Test
+ public void testBootLoopDetection_doesNotMeetThresholdRecoverabilityHighImpact() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_80);
+ watchdog.registerHealthObserver(bootObserver);
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - 1; i++) {
+ watchdog.noteBoot();
+ }
+ assertThat(bootObserver.mitigatedBootLoop()).isFalse();
+ }
+
+ /**
* Ensure that boot loop mitigation is done for the observer with the lowest user impact
*/
@Test
public void testBootLoopMitigationDoneForLowestUserImpact() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1);
bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
@@ -1089,11 +1297,28 @@
assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
}
+ @Test
+ public void testBootLoopMitigationDoneForLowestUserImpactRecoverability() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1);
+ bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
+ TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
+ bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
+ watchdog.registerHealthObserver(bootObserver1);
+ watchdog.registerHealthObserver(bootObserver2);
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD; i++) {
+ watchdog.noteBoot();
+ }
+ assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
+ assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
+ }
+
/**
* Ensure that the correct mitigation counts are sent to the boot loop observer.
*/
@Test
public void testMultipleBootLoopMitigation() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
watchdog.registerHealthObserver(bootObserver);
@@ -1114,6 +1339,64 @@
assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
}
+ @Test
+ public void testMultipleBootLoopMitigationRecoverabilityLowImpact() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
+ watchdog.registerHealthObserver(bootObserver);
+ for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; j++) {
+ watchdog.noteBoot();
+ }
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) {
+ watchdog.noteBoot();
+ }
+ }
+
+ moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1);
+
+ for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; j++) {
+ watchdog.noteBoot();
+ }
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) {
+ watchdog.noteBoot();
+ }
+ }
+
+ assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
+ }
+
+ @Test
+ public void testMultipleBootLoopMitigationRecoverabilityHighImpact() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_80);
+ watchdog.registerHealthObserver(bootObserver);
+ for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - 1; j++) {
+ watchdog.noteBoot();
+ }
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) {
+ watchdog.noteBoot();
+ }
+ }
+
+ moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1);
+
+ for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - 1; j++) {
+ watchdog.noteBoot();
+ }
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) {
+ watchdog.noteBoot();
+ }
+ }
+
+ assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
+ }
+
/**
* Ensure that passing a null list of failed packages does not cause any mitigation logic to
* execute.
@@ -1304,6 +1587,78 @@
}
/**
+ * Ensure that a {@link ObserverInternal} may be correctly written and read in order to persist
+ * across reboots.
+ */
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void testWritingAndReadingObserverInternalRecoverability() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+
+ LongArrayQueue mitigationCalls = new LongArrayQueue();
+ mitigationCalls.addLast(1000);
+ mitigationCalls.addLast(2000);
+ mitigationCalls.addLast(3000);
+ MonitoredPackage writePkg = watchdog.newMonitoredPackage(
+ "test.package", 1000, 2000, true, mitigationCalls);
+ final int bootMitigationCount = 4;
+ ObserverInternal writeObserver = new ObserverInternal("test", List.of(writePkg),
+ bootMitigationCount);
+
+ // Write the observer
+ File tmpFile = File.createTempFile("observer-watchdog-test", ".xml");
+ AtomicFile testFile = new AtomicFile(tmpFile);
+ FileOutputStream stream = testFile.startWrite();
+ TypedXmlSerializer outputSerializer = Xml.resolveSerializer(stream);
+ outputSerializer.startDocument(null, true);
+ writeObserver.writeLocked(outputSerializer);
+ outputSerializer.endDocument();
+ testFile.finishWrite(stream);
+
+ // Read the observer
+ TypedXmlPullParser parser = Xml.resolvePullParser(testFile.openRead());
+ XmlUtils.beginDocument(parser, "observer");
+ ObserverInternal readObserver = ObserverInternal.read(parser, watchdog);
+
+ assertThat(readObserver.name).isEqualTo(writeObserver.name);
+ assertThat(readObserver.getBootMitigationCount()).isEqualTo(bootMitigationCount);
+ }
+
+ /**
+ * Ensure that boot mitigation counts may be correctly written and read as metadata
+ * in order to persist across reboots.
+ */
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void testWritingAndReadingMetadataBootMitigationCountRecoverability() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ String filePath = InstrumentationRegistry.getContext().getFilesDir().toString()
+ + "metadata_file.txt";
+
+ ObserverInternal observer1 = new ObserverInternal("test1", List.of(), 1);
+ ObserverInternal observer2 = new ObserverInternal("test2", List.of(), 2);
+ watchdog.registerObserverInternal(observer1);
+ watchdog.registerObserverInternal(observer2);
+
+ mSpyBootThreshold = spy(watchdog.new BootThreshold(
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT));
+
+ watchdog.saveAllObserversBootMitigationCountToMetadata(filePath);
+
+ observer1.setBootMitigationCount(0);
+ observer2.setBootMitigationCount(0);
+ assertThat(observer1.getBootMitigationCount()).isEqualTo(0);
+ assertThat(observer2.getBootMitigationCount()).isEqualTo(0);
+
+ mSpyBootThreshold.readAllObserversBootMitigationCountIfNecessary(filePath);
+
+ assertThat(observer1.getBootMitigationCount()).isEqualTo(1);
+ assertThat(observer2.getBootMitigationCount()).isEqualTo(2);
+ }
+
+ /**
* Tests device config changes are propagated correctly.
*/
@Test
@@ -1440,11 +1795,19 @@
// Mock CrashRecoveryProperties as they cannot be accessed due to SEPolicy restrictions
private void mockCrashRecoveryProperties(PackageWatchdog watchdog) {
+ mCrashRecoveryPropertiesMap = new HashMap<>();
+
try {
- mSpyBootThreshold = spy(watchdog.new BootThreshold(
- PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
- PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS));
- mCrashRecoveryPropertiesMap = new HashMap<>();
+ if (Flags.recoverabilityDetection()) {
+ mSpyBootThreshold = spy(watchdog.new BootThreshold(
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT));
+ } else {
+ mSpyBootThreshold = spy(watchdog.new BootThreshold(
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS));
+ }
doAnswer((Answer<Integer>) invocationOnMock -> {
String storedValue = mCrashRecoveryPropertiesMap
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
index 07e0e73..0f7ce68 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
@@ -23,6 +23,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@@ -33,7 +34,7 @@
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
-public class AndroidSafetyLabel {
+public class AndroidSafetyLabel implements AslMarshallable {
public enum Format {
NULL, HUMAN_READABLE, ON_DEVICE;
@@ -45,31 +46,55 @@
return mSafetyLabels;
}
- private AndroidSafetyLabel(SafetyLabels safetyLabels) {
+ public AndroidSafetyLabel(SafetyLabels safetyLabels) {
this.mSafetyLabels = safetyLabels;
}
/** Reads a {@link AndroidSafetyLabel} from an {@link InputStream}. */
- // TODO(b/329902686): Support conversion in both directions, specified by format.
+ // TODO(b/329902686): Support parsing from on-device.
public static AndroidSafetyLabel readFromStream(InputStream in, Format format)
throws IOException, ParserConfigurationException, SAXException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
Document document = factory.newDocumentBuilder().parse(in);
- Element appMetadataBundles =
- XmlUtils.getSingleElement(document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES);
+ switch (format) {
+ case HUMAN_READABLE:
+ Element appMetadataBundles =
+ XmlUtils.getSingleElement(document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES);
- return AndroidSafetyLabel.createFromHrElement(appMetadataBundles);
+ return new AndroidSafetyLabelFactory()
+ .createFromHrElements(
+ XmlUtils.asElementList(
+ document.getElementsByTagName(
+ XmlUtils.HR_TAG_APP_METADATA_BUNDLES)));
+ case ON_DEVICE:
+ throw new IllegalArgumentException(
+ "Parsing from on-device format is not supported at this time.");
+ default:
+ throw new IllegalStateException("Unrecognized input format.");
+ }
}
/** Write the content of the {@link AndroidSafetyLabel} to a {@link OutputStream}. */
- // TODO(b/329902686): Support conversion in both directions, specified by format.
+ // TODO(b/329902686): Support outputting human-readable format.
public void writeToStream(OutputStream out, Format format)
throws IOException, ParserConfigurationException, TransformerException {
var docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
var document = docBuilder.newDocument();
- document.appendChild(this.toOdDomElement(document));
+
+ switch (format) {
+ case HUMAN_READABLE:
+ throw new IllegalArgumentException(
+ "Outputting human-readable format is not supported at this time.");
+ case ON_DEVICE:
+ for (var child : this.toOdDomElements(document)) {
+ document.appendChild(child);
+ }
+ break;
+ default:
+ throw new IllegalStateException("Unrecognized input format.");
+ }
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
@@ -81,19 +106,12 @@
transformer.transform(domSource, streamResult);
}
- /** Creates an {@link AndroidSafetyLabel} from human-readable DOM element */
- public static AndroidSafetyLabel createFromHrElement(Element appMetadataBundlesEle) {
- Element safetyLabelsEle =
- XmlUtils.getSingleElement(appMetadataBundlesEle, XmlUtils.HR_TAG_SAFETY_LABELS);
- SafetyLabels safetyLabels = SafetyLabels.createFromHrElement(safetyLabelsEle);
- return new AndroidSafetyLabel(safetyLabels);
- }
-
/** Creates an on-device DOM element from an {@link AndroidSafetyLabel} */
- public Element toOdDomElement(Document doc) {
+ @Override
+ public List<Element> toOdDomElements(Document doc) {
Element aslEle = doc.createElement(XmlUtils.OD_TAG_BUNDLE);
- aslEle.appendChild(mSafetyLabels.toOdDomElement(doc));
- return aslEle;
+ XmlUtils.appendChildren(aslEle, mSafetyLabels.toOdDomElements(doc));
+ return List.of(aslEle);
}
public static void test() {
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
new file mode 100644
index 0000000..9b0f05b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 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.asllib;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class AndroidSafetyLabelFactory implements AslMarshallableFactory<AndroidSafetyLabel> {
+
+ /** Creates an {@link AndroidSafetyLabel} from human-readable DOM element */
+ @Override
+ public AndroidSafetyLabel createFromHrElements(List<Element> appMetadataBundles) {
+ Element appMetadataBundlesEle = XmlUtils.getSingleElement(appMetadataBundles);
+ Element safetyLabelsEle =
+ XmlUtils.getSingleChildElement(
+ appMetadataBundlesEle, XmlUtils.HR_TAG_SAFETY_LABELS);
+ SafetyLabels safetyLabels =
+ new SafetyLabelsFactory().createFromHrElements(List.of(safetyLabelsEle));
+ return new AndroidSafetyLabel(safetyLabels);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java
new file mode 100644
index 0000000..4e64ab0
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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.asllib;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public interface AslMarshallable {
+
+ /** Creates the on-device DOM element from the AslMarshallable Java Object. */
+ List<Element> toOdDomElements(Document doc);
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java
new file mode 100644
index 0000000..b607353
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.asllib;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public interface AslMarshallableFactory<T extends AslMarshallable> {
+
+ /** Creates an {@link AslMarshallableFactory} from human-readable DOM element */
+ T createFromHrElements(List<Element> elements);
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java
index efdaa40..e5ed63b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java
@@ -16,6 +16,10 @@
package com.android.asllib;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
import java.util.Map;
/**
@@ -23,21 +27,32 @@
* are defined in {@link DataCategoryConstants}, each category has a valid set of types {@link
* DataType}, which are mapped in {@link DataTypeConstants}
*/
-public class DataCategory {
+public class DataCategory implements AslMarshallable {
+ private final String mCategoryName;
private final Map<String, DataType> mDataTypes;
- private DataCategory(Map<String, DataType> dataTypes) {
+ public DataCategory(String categoryName, Map<String, DataType> dataTypes) {
+ this.mCategoryName = categoryName;
this.mDataTypes = dataTypes;
}
+ public String getCategoryName() {
+ return mCategoryName;
+ }
+
/** Return the type {@link Map} of String type key to {@link DataType} */
public Map<String, DataType> getDataTypes() {
return mDataTypes;
}
- /** Creates a {@link DataCategory} given map of {@param dataTypes}. */
- public static DataCategory create(Map<String, DataType> dataTypes) {
- return new DataCategory(dataTypes);
+ /** Creates on-device DOM element(s) from the {@link DataCategory}. */
+ @Override
+ public List<Element> toOdDomElements(Document doc) {
+ Element dataCategoryEle = XmlUtils.createPbundleEleWithName(doc, this.getCategoryName());
+ for (DataType dataType : mDataTypes.values()) {
+ XmlUtils.appendChildren(dataCategoryEle, dataType.toOdDomElements(doc));
+ }
+ return List.of(dataCategoryEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
new file mode 100644
index 0000000..5a52591
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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.asllib;
+
+import org.w3c.dom.Element;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DataCategoryFactory implements AslMarshallableFactory<DataCategory> {
+ @Override
+ public DataCategory createFromHrElements(List<Element> elements) {
+ String categoryName = null;
+ Map<String, DataType> dataTypeMap = new HashMap<String, DataType>();
+ for (Element ele : elements) {
+ categoryName = ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY);
+ String dataTypeName = ele.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE);
+ dataTypeMap.put(dataTypeName, new DataTypeFactory().createFromHrElements(List.of(ele)));
+ }
+
+ return new DataCategory(categoryName, dataTypeMap);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
index d2c3d75b..d2fffc0 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
@@ -18,16 +18,15 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
* Data label representation with data shared and data collected maps containing zero or more {@link
* DataCategory}
*/
-public class DataLabels {
+public class DataLabels implements AslMarshallable {
private final Map<String, DataCategory> mDataAccessed;
private final Map<String, DataCategory> mDataCollected;
private final Map<String, DataCategory> mDataShared;
@@ -65,46 +64,9 @@
return mDataShared;
}
- /** Creates a {@link DataLabels} from the human-readable DOM element. */
- public static DataLabels createFromHrElement(Element ele) {
- Map<String, DataCategory> dataAccessed =
- getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_ACCESSED);
- Map<String, DataCategory> dataCollected =
- getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED);
- Map<String, DataCategory> dataShared =
- getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED);
- return new DataLabels(dataAccessed, dataCollected, dataShared);
- }
-
- private static Map<String, DataCategory> getDataCategoriesWithTag(
- Element dataLabelsEle, String dataCategoryUsageTypeTag) {
- Map<String, Map<String, DataType>> dataTypeMap =
- new HashMap<String, Map<String, DataType>>();
- NodeList dataSharedNodeList = dataLabelsEle.getElementsByTagName(dataCategoryUsageTypeTag);
-
- for (int i = 0; i < dataSharedNodeList.getLength(); i++) {
- Element dataSharedEle = (Element) dataSharedNodeList.item(i);
- String dataCategoryName = dataSharedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY);
- String dataTypeName = dataSharedEle.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE);
-
- if (!dataTypeMap.containsKey((dataCategoryName))) {
- dataTypeMap.put(dataCategoryName, new HashMap<String, DataType>());
- }
- dataTypeMap
- .get(dataCategoryName)
- .put(dataTypeName, DataType.createFromHrElement(dataSharedEle));
- }
-
- Map<String, DataCategory> dataCategoryMap = new HashMap<String, DataCategory>();
- for (String dataCategoryName : dataTypeMap.keySet()) {
- Map<String, DataType> dataTypes = dataTypeMap.get(dataCategoryName);
- dataCategoryMap.put(dataCategoryName, DataCategory.create(dataTypes));
- }
- return dataCategoryMap;
- }
-
/** Gets the on-device DOM element for the {@link DataLabels}. */
- public Element toOdDomElement(Document doc) {
+ @Override
+ public List<Element> toOdDomElements(Document doc) {
Element dataLabelsEle =
XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DATA_LABELS);
@@ -112,7 +74,7 @@
maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_COLLECTED);
maybeAppendDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.OD_NAME_DATA_SHARED);
- return dataLabelsEle;
+ return List.of(dataLabelsEle);
}
private void maybeAppendDataUsages(
@@ -130,47 +92,10 @@
DataCategory dataCategory = dataCategoriesMap.get(dataCategoryName);
for (String dataTypeName : dataCategory.getDataTypes().keySet()) {
DataType dataType = dataCategory.getDataTypes().get(dataTypeName);
- Element dataTypeEle = XmlUtils.createPbundleEleWithName(doc, dataTypeName);
- if (!dataType.getPurposeSet().isEmpty()) {
- Element purposesEle = doc.createElement(XmlUtils.OD_TAG_INT_ARRAY);
- purposesEle.setAttribute(XmlUtils.OD_ATTR_NAME, XmlUtils.OD_NAME_PURPOSES);
- purposesEle.setAttribute(
- XmlUtils.OD_ATTR_NUM, String.valueOf(dataType.getPurposeSet().size()));
- for (DataType.Purpose purpose : dataType.getPurposeSet()) {
- Element purposeEle = doc.createElement(XmlUtils.OD_TAG_ITEM);
- purposeEle.setAttribute(
- XmlUtils.OD_ATTR_VALUE, String.valueOf(purpose.getValue()));
- purposesEle.appendChild(purposeEle);
- }
- dataTypeEle.appendChild(purposesEle);
- }
-
- maybeAddBoolToOdElement(
- doc,
- dataTypeEle,
- dataType.getIsCollectionOptional(),
- XmlUtils.OD_NAME_IS_COLLECTION_OPTIONAL);
- maybeAddBoolToOdElement(
- doc,
- dataTypeEle,
- dataType.getIsSharingOptional(),
- XmlUtils.OD_NAME_IS_SHARING_OPTIONAL);
- maybeAddBoolToOdElement(
- doc, dataTypeEle, dataType.getEphemeral(), XmlUtils.OD_NAME_EPHEMERAL);
-
- dataCategoryEle.appendChild(dataTypeEle);
+ XmlUtils.appendChildren(dataCategoryEle, dataType.toOdDomElements(doc));
}
dataUsageEle.appendChild(dataCategoryEle);
}
dataLabelsEle.appendChild(dataUsageEle);
}
-
- private static void maybeAddBoolToOdElement(
- Document doc, Element parentEle, Boolean b, String odName) {
- if (b == null) {
- return;
- }
- Element ele = XmlUtils.createOdBooleanEle(doc, odName, b);
- parentEle.appendChild(ele);
- }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
new file mode 100644
index 0000000..c758ab9
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 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.asllib;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
+
+ /** Creates a {@link DataLabels} from the human-readable DOM element. */
+ @Override
+ public DataLabels createFromHrElements(List<Element> elements) {
+ Element ele = XmlUtils.getSingleElement(elements);
+ Map<String, DataCategory> dataAccessed =
+ getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_ACCESSED);
+ Map<String, DataCategory> dataCollected =
+ getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED);
+ Map<String, DataCategory> dataShared =
+ getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED);
+ return new DataLabels(dataAccessed, dataCollected, dataShared);
+ }
+
+ private static Map<String, DataCategory> getDataCategoriesWithTag(
+ Element dataLabelsEle, String dataCategoryUsageTypeTag) {
+ Map<String, Map<String, DataType>> dataTypeMap =
+ new HashMap<String, Map<String, DataType>>();
+ NodeList dataUsedNodeList = dataLabelsEle.getElementsByTagName(dataCategoryUsageTypeTag);
+ Map<String, DataCategory> dataCategoryMap = new HashMap<String, DataCategory>();
+
+ Set<String> dataCategoryNames = new HashSet<String>();
+ for (int i = 0; i < dataUsedNodeList.getLength(); i++) {
+ Element dataUsedEle = (Element) dataUsedNodeList.item(i);
+ String dataCategoryName = dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY);
+ dataCategoryNames.add(dataCategoryName);
+ }
+ for (String dataCategoryName : dataCategoryNames) {
+ var dataCategoryElements =
+ XmlUtils.asElementList(dataUsedNodeList).stream()
+ .filter(
+ ele ->
+ ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY)
+ .equals(dataCategoryName))
+ .toList();
+ DataCategory dataCategory =
+ new DataCategoryFactory().createFromHrElements(dataCategoryElements);
+ dataCategoryMap.put(dataCategoryName, dataCategory);
+ }
+ return dataCategoryMap;
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
index 7451c69..5ba2975 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
@@ -16,17 +16,18 @@
package com.android.asllib;
+import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import java.util.Arrays;
+import java.util.List;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* Data usage type representation. Types are specific to a {@link DataCategory} and contains
* metadata related to the data usage purpose.
*/
-public class DataType {
+public class DataType implements AslMarshallable {
+
public enum Purpose {
PURPOSE_APP_FUNCTIONALITY(1),
PURPOSE_ANALYTICS(2),
@@ -78,22 +79,30 @@
}
}
+ private final String mDataTypeName;
+
private final Set<Purpose> mPurposeSet;
private final Boolean mIsCollectionOptional;
private final Boolean mIsSharingOptional;
private final Boolean mEphemeral;
- private DataType(
+ public DataType(
+ String dataTypeName,
Set<Purpose> purposeSet,
Boolean isCollectionOptional,
Boolean isSharingOptional,
Boolean ephemeral) {
+ this.mDataTypeName = dataTypeName;
this.mPurposeSet = purposeSet;
this.mIsCollectionOptional = isCollectionOptional;
this.mIsSharingOptional = isSharingOptional;
this.mEphemeral = ephemeral;
}
+ public String getDataTypeName() {
+ return mDataTypeName;
+ }
+
/**
* Returns {@link Set} of valid {@link Integer} purposes for using the associated data category
* and type
@@ -126,20 +135,42 @@
return mEphemeral;
}
- /** Creates a {@link DataType} from the human-readable DOM element. */
- public static DataType createFromHrElement(Element hrDataTypeEle) {
- Set<Purpose> purposeSet =
- Arrays.stream(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_PURPOSES).split("\\|"))
- .map(Purpose::forString)
- .collect(Collectors.toUnmodifiableSet());
- Boolean isCollectionOptional =
- XmlUtils.fromString(
- hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL));
- Boolean isSharingOptional =
- XmlUtils.fromString(
- hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL));
- Boolean ephemeral =
- XmlUtils.fromString(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_EPHEMERAL));
- return new DataType(purposeSet, isCollectionOptional, isSharingOptional, ephemeral);
+ @Override
+ public List<Element> toOdDomElements(Document doc) {
+ Element dataTypeEle = XmlUtils.createPbundleEleWithName(doc, this.getDataTypeName());
+ if (!this.getPurposeSet().isEmpty()) {
+ Element purposesEle = doc.createElement(XmlUtils.OD_TAG_INT_ARRAY);
+ purposesEle.setAttribute(XmlUtils.OD_ATTR_NAME, XmlUtils.OD_NAME_PURPOSES);
+ purposesEle.setAttribute(
+ XmlUtils.OD_ATTR_NUM, String.valueOf(this.getPurposeSet().size()));
+ for (DataType.Purpose purpose : this.getPurposeSet()) {
+ Element purposeEle = doc.createElement(XmlUtils.OD_TAG_ITEM);
+ purposeEle.setAttribute(XmlUtils.OD_ATTR_VALUE, String.valueOf(purpose.getValue()));
+ purposesEle.appendChild(purposeEle);
+ }
+ dataTypeEle.appendChild(purposesEle);
+ }
+
+ maybeAddBoolToOdElement(
+ doc,
+ dataTypeEle,
+ this.getIsCollectionOptional(),
+ XmlUtils.OD_NAME_IS_COLLECTION_OPTIONAL);
+ maybeAddBoolToOdElement(
+ doc,
+ dataTypeEle,
+ this.getIsSharingOptional(),
+ XmlUtils.OD_NAME_IS_SHARING_OPTIONAL);
+ maybeAddBoolToOdElement(doc, dataTypeEle, this.getEphemeral(), XmlUtils.OD_NAME_EPHEMERAL);
+ return List.of(dataTypeEle);
+ }
+
+ private static void maybeAddBoolToOdElement(
+ Document doc, Element parentEle, Boolean b, String odName) {
+ if (b == null) {
+ return;
+ }
+ Element ele = XmlUtils.createOdBooleanEle(doc, odName, b);
+ parentEle.appendChild(ele);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
new file mode 100644
index 0000000..99f8a8b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 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.asllib;
+
+import org.w3c.dom.Element;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class DataTypeFactory implements AslMarshallableFactory<DataType> {
+ /** Creates a {@link DataType} from the human-readable DOM element. */
+ @Override
+ public DataType createFromHrElements(List<Element> elements) {
+ Element hrDataTypeEle = XmlUtils.getSingleElement(elements);
+ String dataTypeName = hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE);
+ Set<DataType.Purpose> purposeSet =
+ Arrays.stream(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_PURPOSES).split("\\|"))
+ .map(DataType.Purpose::forString)
+ .collect(Collectors.toUnmodifiableSet());
+ Boolean isCollectionOptional =
+ XmlUtils.fromString(
+ hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL));
+ Boolean isSharingOptional =
+ XmlUtils.fromString(
+ hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL));
+ Boolean ephemeral =
+ XmlUtils.fromString(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_EPHEMERAL));
+ return new DataType(
+ dataTypeName, purposeSet, isCollectionOptional, isSharingOptional, ephemeral);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
index 6ba15e1..f06522f 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
@@ -19,13 +19,15 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import java.util.List;
+
/** Safety Label representation containing zero or more {@link DataCategory} for data shared */
-public class SafetyLabels {
+public class SafetyLabels implements AslMarshallable {
private final Long mVersion;
private final DataLabels mDataLabels;
- private SafetyLabels(Long version, DataLabels dataLabels) {
+ public SafetyLabels(Long version, DataLabels dataLabels) {
this.mVersion = version;
this.mDataLabels = dataLabels;
}
@@ -40,26 +42,12 @@
return mVersion;
}
- /** Creates a {@link SafetyLabels} from the human-readable DOM element. */
- public static SafetyLabels createFromHrElement(Element safetyLabelsEle) {
- Long version;
- try {
- version = Long.parseLong(safetyLabelsEle.getAttribute(XmlUtils.HR_ATTR_VERSION));
- } catch (Exception e) {
- throw new IllegalArgumentException(
- "Malformed or missing required version in safety labels.");
- }
- Element dataLabelsEle =
- XmlUtils.getSingleElement(safetyLabelsEle, XmlUtils.HR_TAG_DATA_LABELS);
- DataLabels dataLabels = DataLabels.createFromHrElement(dataLabelsEle);
- return new SafetyLabels(version, dataLabels);
- }
-
/** Creates an on-device DOM element from the {@link SafetyLabels}. */
- public Element toOdDomElement(Document doc) {
+ @Override
+ public List<Element> toOdDomElements(Document doc) {
Element safetyLabelsEle =
XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SAFETY_LABELS);
- safetyLabelsEle.appendChild(mDataLabels.toOdDomElement(doc));
- return safetyLabelsEle;
+ XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toOdDomElements(doc));
+ return List.of(safetyLabelsEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
new file mode 100644
index 0000000..68e83fe
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 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.asllib;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels> {
+
+ /** Creates a {@link SafetyLabels} from the human-readable DOM element. */
+ @Override
+ public SafetyLabels createFromHrElements(List<Element> elements) {
+ Element safetyLabelsEle = XmlUtils.getSingleElement(elements);
+ Long version;
+ try {
+ version = Long.parseLong(safetyLabelsEle.getAttribute(XmlUtils.HR_ATTR_VERSION));
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ "Malformed or missing required version in safety labels.");
+ }
+
+ DataLabels dataLabels =
+ new DataLabelsFactory()
+ .createFromHrElements(
+ List.of(
+ XmlUtils.getSingleChildElement(
+ safetyLabelsEle, XmlUtils.HR_TAG_DATA_LABELS)));
+ return new SafetyLabels(version, dataLabels);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
index 4392c2c..3c89a30 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
@@ -20,6 +20,9 @@
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
+import java.util.ArrayList;
+import java.util.List;
+
public class XmlUtils {
public static final String HR_TAG_APP_METADATA_BUNDLES = "app-metadata-bundles";
public static final String HR_TAG_SAFETY_LABELS = "safety-labels";
@@ -60,30 +63,60 @@
/** Gets the single top-level {@link Element} having the {@param tagName}. */
public static Element getSingleElement(Document doc, String tagName) {
var elements = doc.getElementsByTagName(tagName);
- return getSingleElement(elements, tagName);
+ return getSingleElement(elements);
}
/**
* Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
*/
- public static Element getSingleElement(Element parentEle, String tagName) {
+ public static Element getSingleChildElement(Element parentEle, String tagName) {
var elements = parentEle.getElementsByTagName(tagName);
- return getSingleElement(elements, tagName);
+ return getSingleElement(elements);
}
- /** Gets the single {@link Element} from {@param elements} and having the {@param tagName}. */
- public static Element getSingleElement(NodeList elements, String tagName) {
+ /** Gets the single {@link Element} from {@param elements} */
+ public static Element getSingleElement(NodeList elements) {
if (elements.getLength() != 1) {
throw new IllegalArgumentException(
- String.format("Expected 1 %s but got %s.", tagName, elements.getLength()));
+ String.format(
+ "Expected 1 element in NodeList but got %s.", elements.getLength()));
}
var elementAsNode = elements.item(0);
if (!(elementAsNode instanceof Element)) {
- throw new IllegalStateException(String.format("%s was not an element.", tagName));
+ throw new IllegalStateException(
+ String.format("%s was not an element.", elementAsNode.getNodeName()));
}
return ((Element) elementAsNode);
}
+ /** Gets the single {@link Element} within {@param elements}. */
+ public static Element getSingleElement(List<Element> elements) {
+ if (elements.size() != 1) {
+ throw new IllegalStateException(
+ String.format("Expected 1 element in list but got %s.", elements.size()));
+ }
+ return elements.get(0);
+ }
+
+ /** Converts {@param nodeList} into List of {@link Element}. */
+ public static List<Element> asElementList(NodeList nodeList) {
+ List<Element> elementList = new ArrayList<Element>();
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ var elementAsNode = nodeList.item(0);
+ if (elementAsNode instanceof Element) {
+ elementList.add(((Element) elementAsNode));
+ }
+ }
+ return elementList;
+ }
+
+ /** Appends {@param children} to the {@param ele}. */
+ public static void appendChildren(Element ele, List<Element> children) {
+ for (Element c : children) {
+ ele.appendChild(c);
+ }
+ }
+
/** Gets the Boolean from the String value. */
public static Boolean fromString(String s) {
if (s == null) {