Merge "Introducing HDRClamper" into main
diff --git a/api/gen_combined_removed_dex.sh b/api/gen_combined_removed_dex.sh
index 9225fe8..71f366a 100755
--- a/api/gen_combined_removed_dex.sh
+++ b/api/gen_combined_removed_dex.sh
@@ -6,6 +6,6 @@
# Convert each removed.txt to the "dex format" equivalent, and print all output.
for f in "$@"; do
- "$metalava_path" --no-banner "$f" --dex-api "${tmp_dir}/tmp"
+ "$metalava_path" "$f" --dex-api "${tmp_dir}/tmp"
cat "${tmp_dir}/tmp"
done
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1a0bd85..e49ed18 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3484,6 +3484,7 @@
field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
field public static final String TETHERING_SERVICE = "tethering";
+ field public static final String THREAD_NETWORK_SERVICE = "thread_network";
field public static final String TIME_MANAGER_SERVICE = "time_manager";
field public static final String TRANSLATION_MANAGER_SERVICE = "translation";
field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a8a6eb9..a4cc446 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -50,6 +50,7 @@
field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
field public static final String SET_GAME_SERVICE = "android.permission.SET_GAME_SERVICE";
field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
+ field public static final String START_ACTIVITIES_FROM_SDK_SANDBOX = "android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD";
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 00e546a..0191201 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3720,7 +3720,19 @@
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
- if (r.packageInfo == null) {
+
+ if (getInstrumentation() != null
+ && getInstrumentation().getContext() != null
+ && getInstrumentation().getContext().getApplicationInfo() != null
+ && getInstrumentation().isSdkSandboxAllowedToStartActivities()) {
+ // Activities launched from CTS-in-sandbox tests use a customized ApplicationInfo. See
+ // also {@link SdkSandboxManagerLocal#getSdkSandboxApplicationInfoForInstrumentation}.
+ r.packageInfo =
+ getPackageInfo(
+ getInstrumentation().getContext().getApplicationInfo(),
+ mCompatibilityInfo,
+ Context.CONTEXT_INCLUDE_CODE);
+ } else if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, mCompatibilityInfo,
Context.CONTEXT_INCLUDE_CODE);
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index e31486f..10747bb 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -26,6 +26,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerGlobal;
@@ -474,6 +475,56 @@
sr.waitForComplete();
}
+ boolean isSdkSandboxAllowedToStartActivities() {
+ return Process.isSdkSandbox()
+ && mThread != null
+ && mThread.mBoundApplication != null
+ && mThread.mBoundApplication.isSdkInSandbox
+ && getContext() != null
+ && (getContext()
+ .checkSelfPermission(
+ android.Manifest.permission
+ .START_ACTIVITIES_FROM_SDK_SANDBOX)
+ == PackageManager.PERMISSION_GRANTED);
+ }
+
+ /**
+ * Activity name resolution for CTS-in-SdkSandbox tests requires some adjustments. Intents
+ * generated using {@link Context#getPackageName()} use the SDK sandbox package name in the
+ * component field instead of the test package name. An SDK-in-sandbox test attempting to launch
+ * an activity in the test package will encounter name resolution errors when resolving the
+ * activity name in the SDK sandbox package.
+ *
+ * <p>This function replaces the package name of the input intent component to allow activities
+ * belonging to a CTS-in-sandbox test to resolve correctly.
+ *
+ * @param intent the intent to modify to allow CTS-in-sandbox activity resolution.
+ */
+ private void adjustIntentForCtsInSdkSandboxInstrumentation(@NonNull Intent intent) {
+ if (mComponent != null
+ && intent.getComponent() != null
+ && getContext()
+ .getPackageManager()
+ .getSdkSandboxPackageName()
+ .equals(intent.getComponent().getPackageName())) {
+ // Resolve the intent target for the test package, not for the sandbox package.
+ intent.setComponent(
+ new ComponentName(
+ mComponent.getPackageName(), intent.getComponent().getClassName()));
+ }
+ // We match the intent identifier against the running instrumentations for the sandbox.
+ intent.setIdentifier(mComponent.getPackageName());
+ }
+
+ private ActivityInfo resolveActivityInfoForCtsInSandbox(@NonNull Intent intent) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ ActivityInfo ai = intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0);
+ if (ai != null) {
+ ai.processName = mThread.getProcessName();
+ }
+ return ai;
+ }
+
/**
* Start a new activity and wait for it to begin running before returning.
* In addition to being synchronous, this method as some semantic
@@ -531,8 +582,10 @@
synchronized (mSync) {
intent = new Intent(intent);
- ActivityInfo ai = intent.resolveActivityInfo(
- getTargetContext().getPackageManager(), 0);
+ ActivityInfo ai =
+ isSdkSandboxAllowedToStartActivities()
+ ? resolveActivityInfoForCtsInSandbox(intent)
+ : intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0);
if (ai == null) {
throw new RuntimeException("Unable to resolve activity for: " + intent);
}
@@ -1842,6 +1895,9 @@
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -1914,6 +1970,11 @@
IBinder token, Activity target, Intent[] intents, Bundle options,
int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ for (Intent intent : intents) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -1989,6 +2050,9 @@
Context who, IBinder contextThread, IBinder token, String target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -2060,6 +2124,9 @@
Context who, IBinder contextThread, IBinder token, String resultWho,
Intent intent, int requestCode, Bundle options, UserHandle user) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -2110,6 +2177,9 @@
Intent intent, int requestCode, Bundle options,
boolean ignoreTargetSecurity, int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -2161,6 +2231,9 @@
Context who, IBinder contextThread, IAppTask appTask,
Intent intent, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d6dee93..b6a98a5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4089,6 +4089,7 @@
VIBRATOR_MANAGER_SERVICE,
VIBRATOR_SERVICE,
//@hide: STATUS_BAR_SERVICE,
+ THREAD_NETWORK_SERVICE,
CONNECTIVITY_SERVICE,
PAC_PROXY_SERVICE,
VCN_MANAGEMENT_SERVICE,
@@ -4764,6 +4765,20 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.net.thread.ThreadNetworkManager}.
+ *
+ * <p>On devices without {@link PackageManager#FEATURE_THREAD_NETWORK} system feature
+ * the {@link #getSystemService(String)} will return {@code null}.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.thread.ThreadNetworkManager
+ * @hide
+ */
+ @SystemApi
+ public static final String THREAD_NETWORK_SERVICE = "thread_network";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.net.IpSecManager} for encrypting Sockets or Networks with
* IPSec.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e9bbed3..7579d99 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12516,6 +12516,7 @@
return (mFlags & FLAG_ACTIVITY_NEW_DOCUMENT) == FLAG_ACTIVITY_NEW_DOCUMENT;
}
+ // TODO(b/299109198): Refactor into the {@link SdkSandboxManagerLocal}
/** @hide */
public boolean isSandboxActivity(@NonNull Context context) {
if (mAction != null && mAction.equals(ACTION_START_SANDBOXED_ACTIVITY)) {
diff --git a/core/java/android/content/om/OWNERS b/core/java/android/content/om/OWNERS
index 3669817..72aed2d 100644
--- a/core/java/android/content/om/OWNERS
+++ b/core/java/android/content/om/OWNERS
@@ -1,6 +1,5 @@
# Bug component: 568631
-toddke@android.com
-toddke@google.com
patb@google.com
zyy@google.com
+jakmcbane@google.com
\ No newline at end of file
diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl
new file mode 100644
index 0000000..7ab7ed1
--- /dev/null
+++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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.content.pm;
+
+/** @hide */
+parcelable ArchivedActivityParcel {
+ String title;
+ // PNG compressed bitmaps.
+ byte[] iconBitmap;
+ byte[] monochromeIconBitmap;
+}
diff --git a/core/java/android/content/pm/ArchivedPackageParcel.aidl b/core/java/android/content/pm/ArchivedPackageParcel.aidl
index 573e690..d3cd79e 100644
--- a/core/java/android/content/pm/ArchivedPackageParcel.aidl
+++ b/core/java/android/content/pm/ArchivedPackageParcel.aidl
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.content.pm.ArchivedActivityParcel;
import android.content.pm.SigningDetails;
/**
@@ -29,9 +30,8 @@
int versionCode;
int versionCodeMajor;
int targetSdkVersion;
- String backupAllowed;
String defaultToDeviceProtectedStorage;
String requestLegacyExternalStorage;
String userDataFragile;
- String clearUserDataOnFailedRestoreAllowed;
+ ArchivedActivityParcel[] archivedActivities;
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 556c794..94c3b52 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -832,5 +832,5 @@
void unregisterPackageMonitorCallback(IRemoteCallback callback);
- ArchivedPackageParcel getArchivedPackage(in String apkPath);
+ ArchivedPackageParcel getArchivedPackage(in String packageName, int userId);
}
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 149de7e..0333942 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -834,4 +834,11 @@
public abstract V parseServiceAttributes(Resources res,
String packageName, AttributeSet attrs);
+
+ @VisibleForTesting
+ public void unregisterReceivers() {
+ mContext.unregisterReceiver(mPackageReceiver);
+ mContext.unregisterReceiver(mExternalReceiver);
+ mContext.unregisterReceiver(mUserRemovedReceiver);
+ }
}
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 159b789..f3194be 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -23,11 +23,9 @@
import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
import android.content.pm.VerifierInfo;
-import android.os.Build;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DataClass;
-import com.android.internal.util.XmlUtils;
import java.util.List;
import java.util.Set;
@@ -142,32 +140,9 @@
private final boolean mIsSdkLibrary;
/**
- * Set to <code>false</code> if the application does not wish to permit any OS-driven
- * backups of its data; <code>true</code> otherwise.
+ * Archival install info.
*/
- private final boolean mBackupAllowed;
-
- /**
- * When set, the default data storage directory for this app is pointed at
- * the device-protected location.
- */
- private final boolean mDefaultToDeviceProtectedStorage;
-
- /**
- * If {@code true} this app requests full external storage access.
- */
- private final boolean mRequestLegacyExternalStorage;
-
- /**
- * Indicates whether this application has declared its user data as fragile, causing the
- * system to prompt the user on whether to keep the user data on uninstall.
- */
- private final boolean mUserDataFragile;
-
- /**
- * Indicates whether this application's data will be cleared on a failed restore.
- */
- private final boolean mClearUserDataOnFailedRestoreAllowed;
+ private final @Nullable ArchivedPackageParcel mArchivedPackage;
public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit,
String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode,
@@ -179,10 +154,7 @@
String requiredSystemPropertyName, String requiredSystemPropertyValue,
int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
Set<String> requiredSplitTypes, Set<String> splitTypes,
- boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean clearUserDataAllowed,
- boolean backupAllowed, boolean defaultToDeviceProtectedStorage,
- boolean requestLegacyExternalStorage, boolean userDataFragile,
- boolean clearUserDataOnFailedRestoreAllowed) {
+ boolean hasDeviceAdminReceiver, boolean isSdkLibrary) {
mPath = path;
mPackageName = packageName;
mSplitName = splitName;
@@ -216,11 +188,7 @@
mRollbackDataPolicy = rollbackDataPolicy;
mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
mIsSdkLibrary = isSdkLibrary;
- mBackupAllowed = backupAllowed;
- mDefaultToDeviceProtectedStorage = defaultToDeviceProtectedStorage;
- mRequestLegacyExternalStorage = requestLegacyExternalStorage;
- mUserDataFragile = userDataFragile;
- mClearUserDataOnFailedRestoreAllowed = clearUserDataOnFailedRestoreAllowed;
+ mArchivedPackage = null;
}
public ApkLite(String path, ArchivedPackageParcel archivedPackage) {
@@ -257,16 +225,7 @@
mRollbackDataPolicy = 0;
mHasDeviceAdminReceiver = false;
mIsSdkLibrary = false;
- // @see ParsingPackageUtils#parseBaseAppBasicFlags
- mBackupAllowed = XmlUtils.convertValueToBoolean(archivedPackage.backupAllowed, true);
- mDefaultToDeviceProtectedStorage = XmlUtils.convertValueToBoolean(
- archivedPackage.defaultToDeviceProtectedStorage, false);
- mRequestLegacyExternalStorage = XmlUtils.convertValueToBoolean(
- archivedPackage.requestLegacyExternalStorage,
- mTargetSdkVersion < Build.VERSION_CODES.Q);
- mUserDataFragile = XmlUtils.convertValueToBoolean(archivedPackage.userDataFragile, false);
- mClearUserDataOnFailedRestoreAllowed = XmlUtils.convertValueToBoolean(
- archivedPackage.clearUserDataOnFailedRestoreAllowed, true);
+ mArchivedPackage = archivedPackage;
}
/**
@@ -576,53 +535,18 @@
}
/**
- * Set to <code>false</code> if the application does not wish to permit any OS-driven
- * backups of its data; <code>true</code> otherwise.
+ * Archival install info.
*/
@DataClass.Generated.Member
- public boolean isBackupAllowed() {
- return mBackupAllowed;
- }
-
- /**
- * When set, the default data storage directory for this app is pointed at
- * the device-protected location.
- */
- @DataClass.Generated.Member
- public boolean isDefaultToDeviceProtectedStorage() {
- return mDefaultToDeviceProtectedStorage;
- }
-
- /**
- * If {@code true} this app requests full external storage access.
- */
- @DataClass.Generated.Member
- public boolean isRequestLegacyExternalStorage() {
- return mRequestLegacyExternalStorage;
- }
-
- /**
- * Indicates whether this application has declared its user data as fragile, causing the
- * system to prompt the user on whether to keep the user data on uninstall.
- */
- @DataClass.Generated.Member
- public boolean isUserDataFragile() {
- return mUserDataFragile;
- }
-
- /**
- * Indicates whether this application's data will be cleared on a failed restore.
- */
- @DataClass.Generated.Member
- public boolean isClearUserDataOnFailedRestoreAllowed() {
- return mClearUserDataOnFailedRestoreAllowed;
+ public @Nullable ArchivedPackageParcel getArchivedPackage() {
+ return mArchivedPackage;
}
@DataClass.Generated(
- time = 1693513509013L,
+ time = 1694792109463L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mBackupAllowed\nprivate final boolean mDefaultToDeviceProtectedStorage\nprivate final boolean mRequestLegacyExternalStorage\nprivate final boolean mUserDataFragile\nprivate final boolean mClearUserDataOnFailedRestoreAllowed\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 066ff689..5f86742 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -40,7 +40,6 @@
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
@@ -447,13 +446,6 @@
int overlayPriority = 0;
int rollbackDataPolicy = 0;
- boolean clearUserDataAllowed = true;
- boolean backupAllowed = true;
- boolean defaultToDeviceProtectedStorage = false;
- String requestLegacyExternalStorage = null;
- boolean userDataFragile = false;
- boolean clearUserDataOnFailedRestoreAllowed = true;
-
String requiredSystemPropertyName = null;
String requiredSystemPropertyValue = null;
@@ -493,22 +485,6 @@
useEmbeddedDex = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
"useEmbeddedDex", false);
- clearUserDataAllowed = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
- "allowClearUserDataOnFailedRestore", true);
- backupAllowed = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
- "allowBackup", true);
- defaultToDeviceProtectedStorage = parser.getAttributeBooleanValue(
- ANDROID_RES_NAMESPACE,
- "defaultToDeviceProtectedStorage", false);
- userDataFragile = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
- "hasFragileUserData", false);
- clearUserDataOnFailedRestoreAllowed = parser.getAttributeBooleanValue(
- ANDROID_RES_NAMESPACE,
- "allowClearUserDataOnFailedRestore", true);
-
- requestLegacyExternalStorage = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
- "requestLegacyExternalStorage");
-
rollbackDataPolicy = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
"rollbackDataPolicy", 0);
String permission = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
@@ -629,9 +605,6 @@
return input.skip(message);
}
- boolean isRequestLegacyExternalStorage = XmlUtils.convertValueToBoolean(
- requestLegacyExternalStorage, targetSdkVersion < Build.VERSION_CODES.Q);
-
return input.success(
new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
configForSplit, usesSplitName, isSplitRequired, versionCode,
@@ -641,9 +614,7 @@
overlayIsStatic, overlayPriority, requiredSystemPropertyName,
requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
- hasDeviceAdminReceiver, isSdkLibrary, clearUserDataAllowed, backupAllowed,
- defaultToDeviceProtectedStorage, isRequestLegacyExternalStorage,
- userDataFragile, clearUserDataOnFailedRestoreAllowed));
+ hasDeviceAdminReceiver, isSdkLibrary));
}
private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java
index ccef9de..116dd1f 100644
--- a/core/java/android/content/pm/parsing/PackageLite.java
+++ b/core/java/android/content/pm/parsing/PackageLite.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.PackageInfo;
import android.content.pm.SigningDetails;
import android.content.pm.VerifierInfo;
@@ -112,29 +113,11 @@
* Indicates if this package is a sdk.
*/
private final boolean mIsSdkLibrary;
+
/**
- * Set to <code>false</code> if the application does not wish to permit any OS-driven
- * backups of its data; <code>true</code> otherwise.
+ * Archival install info.
*/
- private final boolean mBackupAllowed;
- /**
- * When set, the default data storage directory for this app is pointed at
- * the device-protected location.
- */
- private final boolean mDefaultToDeviceProtectedStorage;
- /**
- * If {@code true} this app requests full external storage access.
- */
- private final boolean mRequestLegacyExternalStorage;
- /**
- * Indicates whether this application has declared its user data as fragile, causing the
- * system to prompt the user on whether to keep the user data on uninstall.
- */
- private final boolean mUserDataFragile;
- /**
- * Indicates whether this application's data will be cleared on a failed restore.
- */
- private final boolean mClearUserDataOnFailedRestoreAllowed;
+ private final @Nullable ArchivedPackageParcel mArchivedPackage;
public PackageLite(String path, String baseApkPath, ApkLite baseApk,
String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames,
@@ -171,11 +154,7 @@
mSplitApkPaths = splitApkPaths;
mSplitRevisionCodes = splitRevisionCodes;
mTargetSdk = targetSdk;
- mBackupAllowed = baseApk.isBackupAllowed();
- mDefaultToDeviceProtectedStorage = baseApk.isDefaultToDeviceProtectedStorage();
- mRequestLegacyExternalStorage = baseApk.isRequestLegacyExternalStorage();
- mUserDataFragile = baseApk.isUserDataFragile();
- mClearUserDataOnFailedRestoreAllowed = baseApk.isClearUserDataOnFailedRestoreAllowed();
+ mArchivedPackage = baseApk.getArchivedPackage();
}
/**
@@ -455,53 +434,18 @@
}
/**
- * Set to <code>false</code> if the application does not wish to permit any OS-driven
- * backups of its data; <code>true</code> otherwise.
+ * Archival install info.
*/
@DataClass.Generated.Member
- public boolean isBackupAllowed() {
- return mBackupAllowed;
- }
-
- /**
- * When set, the default data storage directory for this app is pointed at
- * the device-protected location.
- */
- @DataClass.Generated.Member
- public boolean isDefaultToDeviceProtectedStorage() {
- return mDefaultToDeviceProtectedStorage;
- }
-
- /**
- * If {@code true} this app requests full external storage access.
- */
- @DataClass.Generated.Member
- public boolean isRequestLegacyExternalStorage() {
- return mRequestLegacyExternalStorage;
- }
-
- /**
- * Indicates whether this application has declared its user data as fragile, causing the
- * system to prompt the user on whether to keep the user data on uninstall.
- */
- @DataClass.Generated.Member
- public boolean isUserDataFragile() {
- return mUserDataFragile;
- }
-
- /**
- * Indicates whether this application's data will be cleared on a failed restore.
- */
- @DataClass.Generated.Member
- public boolean isClearUserDataOnFailedRestoreAllowed() {
- return mClearUserDataOnFailedRestoreAllowed;
+ public @Nullable ArchivedPackageParcel getArchivedPackage() {
+ return mArchivedPackage;
}
@DataClass.Generated(
- time = 1693513525097L,
+ time = 1694792176268L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final boolean mBackupAllowed\nprivate final boolean mDefaultToDeviceProtectedStorage\nprivate final boolean mRequestLegacyExternalStorage\nprivate final boolean mUserDataFragile\nprivate final boolean mClearUserDataOnFailedRestoreAllowed\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/res/OWNERS b/core/java/android/content/res/OWNERS
index a7bce12..141d58d 100644
--- a/core/java/android/content/res/OWNERS
+++ b/core/java/android/content/res/OWNERS
@@ -1,8 +1,7 @@
# Bug component: 568761
-toddke@android.com
-toddke@google.com
patb@google.com
zyy@google.com
+branliu@google.com
per-file FontScaleConverter*=fuego@google.com
\ No newline at end of file
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index b48b7ec..3f41c56 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -46,6 +46,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SharedMemory;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.Log;
@@ -131,6 +132,9 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
static final long MULTIPLE_ACTIVE_HOTWORD_DETECTORS = 193232191L;
+ private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED =
+ SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false);
+
IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
@Override
public void ready() {
@@ -947,6 +951,10 @@
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
+ if (!SYSPROP_VISUAL_QUERY_SERVICE_ENABLED) {
+ throw new IllegalStateException("VisualQueryDetectionService is not enabled on this "
+ + "system. Please set ro.hotword.visual_query_service_enabled to true.");
+ }
if (mSystemService == null) {
throw new IllegalStateException("Not available until onReady() is called");
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 85d7c10..fe515cd 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4437,7 +4437,7 @@
* @param drawingPosition the drawing order position.
* @return the container position of a child for this drawing order position.
*
- * @see #getChildDrawingOrder(int, int)}
+ * @see #getChildDrawingOrder(int, int)
*/
public final int getChildDrawingOrder(int drawingPosition) {
return getChildDrawingOrder(getChildCount(), drawingPosition);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 59344b0..05063365 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -15143,6 +15143,9 @@
final ClipDescription description =
getClipboardManagerForUser().getPrimaryClipDescription();
+ if (description == null) {
+ return false;
+ }
final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
return (isPlainType && description.isStyledText())
|| description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index f40874b..7585826 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -237,13 +237,14 @@
PixelFormat.RGBA_8888,
GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
| GraphicBuffer.USAGE_SW_WRITE_RARELY);
- if (background == null) {
+ final Canvas c = background != null ? background.lockCanvas() : null;
+ if (c == null) {
Log.e(TAG, "Unable to draw snapshot: failed to allocate graphic buffer for "
+ mTitle);
+ mTransaction.clear();
+ childSurfaceControl.release();
return;
}
- // TODO: Support this on HardwareBuffer
- final Canvas c = background.lockCanvas();
drawBackgroundAndBars(c, frame);
background.unlockCanvasAndPost(c);
mTransaction.setBuffer(mRootSurface,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d000b23..c09f0a3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7663,6 +7663,10 @@
<permission android:name="android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA"
android:protectionLevel="signature" />
+ <!-- @hide @TestApi Allows tests running in CTS-in-sandbox mode to launch activities -->
+ <permission android:name="android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows the holder to call health connect migration APIs.
@hide -->
<permission android:name="android.permission.MIGRATE_HEALTH_CONNECT_DATA"
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 5553902..37ef6cb 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -85,9 +85,11 @@
assertEquals(2, cache.getAllServicesSize(U0));
assertEquals(2, cache.getPersistentServicesSize(U0));
assertNotEmptyFileCreated(cache, U0);
+ cache.unregisterReceivers();
// Make sure all services can be loaded from xml
cache = new TestServicesCache();
assertEquals(2, cache.getPersistentServicesSize(U0));
+ cache.unregisterReceivers();
}
public void testGetAllServicesReplaceUid() {
@@ -110,6 +112,7 @@
assertTrue("UID must be updated to the new value",
uids.contains(SYSTEM_IMAGE_UID));
assertFalse("UID must be updated to the new value", uids.contains(UID2));
+ cache.unregisterReceivers();
}
public void testGetAllServicesServiceRemoved() {
@@ -118,6 +121,7 @@
cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2));
assertEquals(2, cache.getAllServicesSize(U0));
assertEquals(2, cache.getPersistentServicesSize(U0));
+ cache.unregisterReceivers();
// Re-read data from disk and verify services were saved
cache = new TestServicesCache();
assertEquals(2, cache.getPersistentServicesSize(U0));
@@ -125,6 +129,7 @@
cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
assertEquals(1, cache.getAllServicesSize(U0));
assertEquals(1, cache.getPersistentServicesSize(U0));
+ cache.unregisterReceivers();
}
public void testGetAllServicesMultiUser() {
@@ -137,12 +142,14 @@
assertEquals(1, cache.getAllServicesSize(U1));
assertEquals(1, cache.getPersistentServicesSize(U1));
assertEquals("No services should be available for user 3", 0, cache.getAllServicesSize(3));
+ cache.unregisterReceivers();
// Re-read data from disk and verify services were saved
cache = new TestServicesCache();
assertEquals(1, cache.getPersistentServicesSize(U0));
assertEquals(1, cache.getPersistentServicesSize(U1));
assertNotEmptyFileCreated(cache, U0);
assertNotEmptyFileCreated(cache, U1);
+ cache.unregisterReceivers();
}
public void testOnRemove() {
@@ -158,6 +165,7 @@
cache.clearServicesForQuerying();
assertEquals(1, cache.getAllServicesSize(U0));
assertEquals(0, cache.getAllServicesSize(U1));
+ cache.unregisterReceivers();
}
public void testMigration() {
@@ -186,10 +194,12 @@
cache.addServiceForQuerying(0, r2, newServiceInfo(t2, 2));
assertEquals(2, cache.getAllServicesSize(u0));
assertEquals(0, cache.getAllServicesSize(u1));
+ cache.unregisterReceivers();
// Re-read data from disk. Verify that services were saved and old file was ignored
cache = new TestServicesCache();
assertEquals(2, cache.getPersistentServicesSize(u0));
assertEquals(0, cache.getPersistentServicesSize(u1));
+ cache.unregisterReceivers();
}
private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo(
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
index ace2053..9d3fca8 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
@@ -195,7 +195,7 @@
protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
if (!ACCEPTED_SIGNING_SCHEMES.contains(key.getAlgorithm().toLowerCase())) {
throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
- + ". Only" + Arrays.toString(ACCEPTED_SIGNING_SCHEMES.stream().toArray())
+ + ". Only " + Arrays.toString(ACCEPTED_SIGNING_SCHEMES.stream().toArray())
+ " supported");
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
index 931c2f8..d5fb49a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
@@ -189,7 +189,7 @@
protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
- + ". Only" + KeyProperties.KEY_ALGORITHM_RSA + " supported");
+ + ". Only " + KeyProperties.KEY_ALGORITHM_RSA + " supported");
}
super.initKey(key);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 0112e32..15d14e8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -213,9 +213,6 @@
if (mRearDisplayStateRequest != null || isRearDisplayActive()) {
mRearDisplayStateRequest = null;
mDeviceStateManager.cancelStateRequest();
- } else {
- throw new IllegalStateException(
- "Unable to cancel a rear display session as there is no active session");
}
}
}
@@ -432,10 +429,6 @@
synchronized (mLock) {
if (mRearDisplayPresentationController != null) {
mDeviceStateManager.cancelStateRequest();
- } else {
- throw new IllegalStateException(
- "Unable to cancel a rear display presentation session as there is no "
- + "active session");
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 016771d..e916a14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -226,6 +226,13 @@
private ArrayList<TaskState> mPausingTasks = null;
/**
+ * List of tasks were pausing but closed in a subsequent merged transition. If a
+ * closing task is reopened, the leash is not initially hidden since it is already
+ * visible.
+ */
+ private ArrayList<TaskState> mClosingTasks = null;
+
+ /**
* List of tasks that we are switching to. Upon finish, these will remain visible and
* on top.
*/
@@ -376,6 +383,7 @@
}
mFinishTransaction = null;
mPausingTasks = null;
+ mClosingTasks = null;
mOpeningTasks = null;
mInfo = null;
mTransition = null;
@@ -420,6 +428,7 @@
mFinishCB = finishCB;
mFinishTransaction = finishT;
mPausingTasks = new ArrayList<>();
+ mClosingTasks = new ArrayList<>();
mOpeningTasks = new ArrayList<>();
mLeashMap = new ArrayMap<>();
mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
@@ -671,7 +680,10 @@
final TransitionInfo.Change change = closingTasks.get(i);
final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
if (pausingIdx >= 0) {
- mPausingTasks.remove(pausingIdx);
+ // We are closing the pausing task, but it is still visible and can be
+ // restart by another transition prior to this transition finishing
+ final TaskState closingTask = mPausingTasks.remove(pausingIdx);
+ mClosingTasks.add(closingTask);
didMergeThings = true;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
" closing pausing taskId=%d", change.getTaskInfo().taskId);
@@ -707,7 +719,12 @@
for (int i = 0; i < openingTasks.size(); ++i) {
final TransitionInfo.Change change = openingTasks.get(i);
final boolean isLeaf = openingTaskIsLeafs.get(i) == 1;
- int pausingIdx = TaskState.indexOf(mPausingTasks, change);
+ final int closingIdx = TaskState.indexOf(mClosingTasks, change);
+ if (closingIdx >= 0) {
+ // Remove opening tasks from closing set
+ mClosingTasks.remove(closingIdx);
+ }
+ final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
if (pausingIdx >= 0) {
// Something is showing/opening a previously-pausing app.
if (isLeaf) {
@@ -730,12 +747,14 @@
appearedTargets[nextTargetIdx++] = target;
// reparent into the original `mInfo` since that's where we are animating.
final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+ final boolean wasClosing = closingIdx >= 0;
t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash());
t.setLayer(target.leash, layer);
- // Hide the animation leash, let listener show it.
- t.hide(target.leash);
+ // Hide the animation leash if not already visible, let listener show it
+ t.setVisibility(target.leash, !wasClosing);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " opening new leaf taskId=%d", target.taskId);
+ " opening new leaf taskId=%d wasClosing=%b",
+ target.taskId, wasClosing);
mOpeningTasks.add(new TaskState(change, target.leash));
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index adae21b..93d7636 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -54,12 +54,13 @@
private static final String TAG = TaskViewTaskController.class.getSimpleName();
private final CloseGuard mGuard = new CloseGuard();
-
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ /** Used to inset the activity content to allow space for a caption bar. */
+ private final Binder mCaptionInsetsOwner = new Binder();
private final ShellTaskOrganizer mTaskOrganizer;
private final Executor mShellExecutor;
private final SyncTransactionQueue mSyncQueue;
private final TaskViewTransitions mTaskViewTransitions;
- private TaskViewBase mTaskViewBase;
private final Context mContext;
/**
@@ -70,21 +71,19 @@
* in this situation to allow us to notify listeners correctly if the task failed to open.
*/
private ActivityManager.RunningTaskInfo mPendingInfo;
- /* Indicates that the task we attempted to launch in the task view failed to launch. */
- private boolean mTaskNotFound;
+ private TaskViewBase mTaskViewBase;
protected ActivityManager.RunningTaskInfo mTaskInfo;
private WindowContainerToken mTaskToken;
private SurfaceControl mTaskLeash;
- private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ /* Indicates that the task we attempted to launch in the task view failed to launch. */
+ private boolean mTaskNotFound;
private boolean mSurfaceCreated;
private SurfaceControl mSurfaceControl;
private boolean mIsInitialized;
private boolean mNotifiedForInitialized;
+ private boolean mHideTaskWithSurface = true;
private TaskView.Listener mListener;
private Executor mListenerExecutor;
-
- /** Used to inset the activity content to allow space for a caption bar. */
- private final Binder mCaptionInsetsOwner = new Binder();
private Rect mCaptionInsets;
public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
@@ -102,6 +101,19 @@
mGuard.open("release");
}
+ /**
+ * Specifies if the task should be hidden when the surface is destroyed.
+ * <p>This is {@code true} by default.
+ *
+ * @param hideTaskWithSurface {@code false} if task needs to remain visible even when the
+ * surface is destroyed, {@code true} otherwise.
+ */
+ public void setHideTaskWithSurface(boolean hideTaskWithSurface) {
+ // TODO(b/299535374): Remove mHideTaskWithSurface once the taskviews with launch root tasks
+ // are moved to a window in SystemUI in auto.
+ mHideTaskWithSurface = hideTaskWithSurface;
+ }
+
SurfaceControl getSurfaceControl() {
return mSurfaceControl;
}
@@ -257,9 +269,17 @@
mTaskNotFound = false;
}
+ /** This method shouldn't be called when shell transitions are enabled. */
private void updateTaskVisibility() {
+ boolean visible = mSurfaceCreated;
+ if (!visible && !mHideTaskWithSurface) {
+ return;
+ }
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
+ wct.setHidden(mTaskToken, !visible /* hidden */);
+ if (!visible) {
+ wct.reorder(mTaskToken, false /* onTop */);
+ }
mSyncQueue.queue(wct);
if (mListener == null) {
return;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index d098d33..0088051 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -221,6 +221,20 @@
}
@Test
+ public void testSurfaceDestroyed_withTask_shouldNotHideTask_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
+ mTaskViewTaskController.setHideTaskWithSurface(false);
+
+ SurfaceHolder sh = mock(SurfaceHolder.class);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskView.surfaceCreated(sh);
+ reset(mViewListener);
+ mTaskView.surfaceDestroyed(sh);
+
+ verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+ }
+
+ @Test
public void testSurfaceDestroyed_withTask_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
SurfaceHolder sh = mock(SurfaceHolder.class);
diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS
index 436f107..ef4cc46 100644
--- a/libs/androidfw/OWNERS
+++ b/libs/androidfw/OWNERS
@@ -1,5 +1,4 @@
set noparent
-toddke@google.com
zyy@google.com
patb@google.com
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index b77368a..3a0e51b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -32,8 +32,8 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -49,10 +49,8 @@
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
@@ -73,7 +71,6 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
-import com.android.settingslib.spa.framework.compose.horizontalValues
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
import kotlin.math.abs
@@ -129,16 +126,10 @@
private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) {
Text(
text = title,
- modifier = Modifier
- .padding(
- WindowInsets.navigationBars
- .asPaddingValues()
- .horizontalValues()
- )
- .padding(
- start = SettingsDimension.itemPaddingAround,
- end = SettingsDimension.itemPaddingEnd,
- ),
+ modifier = Modifier.padding(
+ start = SettingsDimension.itemPaddingAround,
+ end = SettingsDimension.itemPaddingEnd,
+ ),
overflow = TextOverflow.Ellipsis,
maxLines = maxLines,
)
@@ -157,6 +148,15 @@
* Represents the colors used by a top app bar in different states.
* This implementation animates the container color according to the top app bar scroll state. It
* does not animate the leading, headline, or trailing colors.
+ *
+ * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a
+ * factory method using the default material3 spec
+ * @param containerColor the color used for the background of this BottomAppBar. Use
+ * [Color.Transparent] to have no color.
+ * @param scrolledContainerColor the container color when content is scrolled behind it
+ * @param navigationIconContentColor the content color used for the navigation icon
+ * @param titleContentColor the content color used for the title
+ * @param actionIconContentColor the content color used for actions
*/
@Stable
private class TopAppBarColors(
@@ -248,7 +248,6 @@
titleTextStyle = titleTextStyle,
titleAlpha = 1f,
titleVerticalArrangement = Arrangement.Center,
- titleHorizontalArrangement = Arrangement.Start,
titleBottomPadding = 0,
hideTitleSemantics = false,
navigationIcon = navigationIcon,
@@ -312,7 +311,7 @@
// This will potentially animate or interpolate a transition between the container color and the
// container's scrolled color according to the app bar's scroll state.
val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f
- val appBarContainerColor by rememberUpdatedState(colors.containerColor(colorTransitionFraction))
+ val appBarContainerColor = colors.containerColor(colorTransitionFraction)
// Wrap the given actions in a Row.
val actionsRow = @Composable {
@@ -364,14 +363,17 @@
titleTextStyle = smallTitleTextStyle,
titleAlpha = topTitleAlpha,
titleVerticalArrangement = Arrangement.Center,
- titleHorizontalArrangement = Arrangement.Start,
titleBottomPadding = 0,
hideTitleSemantics = hideTopRowSemantics,
navigationIcon = navigationIcon,
actions = actionsRow,
)
TopAppBarLayout(
- modifier = Modifier.clipToBounds(),
+ modifier = Modifier
+ // only apply the horizontal sides of the window insets padding, since the top
+ // padding will always be applied by the layout above
+ .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal))
+ .clipToBounds(),
heightPx = maxHeightPx.floatValue - pinnedHeightPx +
(scrollBehavior?.state?.heightOffset ?: 0f),
navigationIconContentColor = colors.navigationIconContentColor,
@@ -392,7 +394,6 @@
titleTextStyle = titleTextStyle,
titleAlpha = bottomTitleAlpha,
titleVerticalArrangement = Arrangement.Bottom,
- titleHorizontalArrangement = Arrangement.Start,
titleBottomPadding = titleBottomPaddingPx,
hideTitleSemantics = hideBottomRowSemantics,
navigationIcon = {},
@@ -419,7 +420,6 @@
* @param modifier a [Modifier]
* @param titleAlpha the title's alpha
* @param titleVerticalArrangement the title's vertical arrangement
- * @param titleHorizontalArrangement the title's horizontal arrangement
* @param titleBottomPadding the title's bottom padding
* @param hideTitleSemantics hides the title node from the semantic tree. Apply this
* boolean when this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics
@@ -440,7 +440,6 @@
titleTextStyle: TextStyle,
titleAlpha: Float,
titleVerticalArrangement: Arrangement.Vertical,
- titleHorizontalArrangement: Arrangement.Horizontal,
titleBottomPadding: Int,
hideTitleSemantics: Boolean,
navigationIcon: @Composable () -> Unit,
@@ -470,10 +469,10 @@
CompositionLocalProvider(
LocalContentColor provides titleContentColor,
LocalDensity provides with(LocalDensity.current) {
- Density(
- density = density,
- fontScale = if (titleScaleDisabled) 1f else fontScale,
- )
+ Density(
+ density = density,
+ fontScale = if (titleScaleDisabled) 1f else fontScale,
+ )
},
content = title
)
@@ -528,15 +527,7 @@
// Title
titlePlaceable.placeRelative(
- x = when (titleHorizontalArrangement) {
- Arrangement.Center -> (constraints.maxWidth - titlePlaceable.width) / 2
- Arrangement.End ->
- constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width
- // Arrangement.Start.
- // A TopAppBarTitleInset will make sure the title is offset in case the
- // navigation icon is missing.
- else -> max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width)
- },
+ x = max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width),
y = when (titleVerticalArrangement) {
Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2
// Apply bottom padding from the title's baseline only when the Arrangement is
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
index a6a5ed22..3dac7db 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
@@ -22,7 +22,6 @@
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -307,8 +306,8 @@
}
/**
- * Checks the app bar's components positioning when it's a [CustomizedTopAppBar], a
- * [CenterAlignedTopAppBar], or a larger app bar that is scrolled up and collapsed into a small
+ * Checks the app bar's components positioning when it's a [CustomizedTopAppBar]
+ * or a larger app bar that is scrolled up and collapsed into a small
* configuration and there is no navigation icon.
*/
private fun assertSmallPositioningWithoutNavigation(isCenteredTitle: Boolean = false) {
@@ -335,8 +334,7 @@
}
/**
- * Checks the app bar's components positioning when it's a [CustomizedTopAppBar] or a
- * [CenterAlignedTopAppBar].
+ * Checks the app bar's components positioning when it's a [CustomizedTopAppBar].
*/
private fun assertSmallDefaultPositioning(isCenteredTitle: Boolean = false) {
val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
deleted file mode 100644
index 54d5c3d..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.net;
-
-import android.app.usage.NetworkStats;
-import android.app.usage.NetworkStatsManager;
-import android.content.Context;
-import android.net.NetworkTemplate;
-import android.util.Log;
-
-import androidx.loader.content.AsyncTaskLoader;
-
-/**
- * Loader for retrieving the network stats summary for all UIDs.
- */
-public class NetworkStatsSummaryLoader extends AsyncTaskLoader<NetworkStats> {
-
- private static final String TAG = "NetworkDetailLoader";
- private final NetworkStatsManager mNetworkStatsManager;
- private final long mStart;
- private final long mEnd;
- private final NetworkTemplate mNetworkTemplate;
-
- private NetworkStatsSummaryLoader(Builder builder) {
- super(builder.mContext);
- mStart = builder.mStart;
- mEnd = builder.mEnd;
- mNetworkTemplate = builder.mNetworkTemplate;
- mNetworkStatsManager = (NetworkStatsManager)
- builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
- }
-
- @Override
- protected void onStartLoading() {
- super.onStartLoading();
- forceLoad();
- }
-
- @Override
- public NetworkStats loadInBackground() {
- try {
- return mNetworkStatsManager.querySummary(mNetworkTemplate, mStart, mEnd);
- } catch (RuntimeException e) {
- Log.e(TAG, "Exception querying network detail.", e);
- return null;
- }
- }
-
- @Override
- protected void onStopLoading() {
- super.onStopLoading();
- cancelLoad();
- }
-
- @Override
- protected void onReset() {
- super.onReset();
- cancelLoad();
- }
-
- public static class Builder {
- private final Context mContext;
- private long mStart;
- private long mEnd;
- private NetworkTemplate mNetworkTemplate;
-
- public Builder(Context context) {
- mContext = context;
- }
-
- public Builder setStartTime(long start) {
- mStart = start;
- return this;
- }
-
- public Builder setEndTime(long end) {
- mEnd = end;
- return this;
- }
-
- /**
- * Set {@link NetworkTemplate} for builder
- */
- public Builder setNetworkTemplate(NetworkTemplate template) {
- mNetworkTemplate = template;
- return this;
- }
-
- public NetworkStatsSummaryLoader build() {
- return new NetworkStatsSummaryLoader(this);
- }
- }
-}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d267d80..15620b7 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -853,6 +853,9 @@
<!-- Permission required for accessing all content provider mime types -->
<uses-permission android:name="android.permission.GET_ANY_PROVIDER_TYPE" />
+ <!-- Permission required for CTS-in-sandbox tests -->
+ <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX" />
+
<!-- Permission required for CTS test - CtsWallpaperTestCases -->
<uses-permission android:name="android.permission.ALWAYS_UPDATE_WALLPAPER" />
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 78da5a6..8f329b3 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -4,7 +4,6 @@
dsandler@android.com
-aaliomer@google.com
aaronjli@google.com
achalke@google.com
acul@google.com
@@ -36,6 +35,7 @@
gwasserman@google.com
hwwang@google.com
hyunyoungs@google.com
+ikateryna@google.com
jaggies@google.com
jamesoleary@google.com
jbolinger@google.com
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
new file mode 100644
index 0000000..7abf9ae
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2023, 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.
+*/
+-->
+
+<!-- This file is needed when flag lockscreen.enable_landscape is on
+ Required for landscape lockscreen on small screens. -->
+<com.android.keyguard.KeyguardPasswordView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/keyguard_password_view"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ androidprv:layout_maxWidth="@dimen/keyguard_security_width"
+ android:layout_gravity="center_horizontal|bottom"
+ android:gravity="bottom">
+
+ <!-- Layout here is visually identical to the previous keyguard_password_view.
+ I.E., 'constraints here effectively the same as the previous linear layout' -->
+ <androidx.constraintlayout.motion.widget.MotionLayout
+ android:id="@+id/password_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical"
+ androidprv:layoutDescription="@xml/keyguard_password_scene">
+
+ <!-- Guideline need to align password right of centre,
+ when on small screen landscape layout -->
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/password_center_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ androidprv:layout_constraintGuide_percent="0.5" />
+
+ <LinearLayout
+ android:id="@+id/keyguard_bouncer_message_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical"
+ androidprv:layout_constraintTop_toTopOf="parent">
+
+ <include layout="@layout/keyguard_bouncer_message_area" />
+
+ <com.android.systemui.bouncer.ui.BouncerMessageView
+ android:id="@+id/bouncer_message_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/passwordEntry_container"
+ android:layout_width="280dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:theme="?attr/passwordStyle"
+ androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintHorizontal_bias="0.5"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@+id/keyguard_bouncer_message_container"
+ androidprv:layout_constraintVertical_bias="0.7777">
+
+ <EditText
+ android:id="@+id/passwordEntry"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/keyguard_accessibility_password"
+ android:gravity="center_horizontal"
+ android:imeOptions="flagForceAscii|actionDone"
+ android:inputType="textPassword"
+ android:maxLength="500"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textCursorDrawable="@null"
+ android:textSize="16sp"
+ android:textStyle="normal" />
+
+ <ImageView
+ android:id="@+id/switch_ime_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end|center_vertical"
+ android:layout_marginBottom="12dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:clickable="true"
+ android:contentDescription="@string/accessibility_ime_switch_button"
+ android:padding="8dip"
+ android:src="@drawable/ic_lockscreen_ime"
+ android:tint="?android:attr/textColorPrimary"
+ android:visibility="gone" />
+ </FrameLayout>
+
+ <include
+ android:id="@+id/keyguard_selector_fade_container"
+ layout="@layout/keyguard_eca"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="12dp"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent" />
+
+ </androidx.constraintlayout.motion.widget.MotionLayout>
+
+</com.android.keyguard.KeyguardPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml
new file mode 100644
index 0000000..092e10d
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<MotionScene
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:motion="http://schemas.android.com/apk/res-auto"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto">
+
+ <Transition
+ motion:constraintSetStart="@id/single_constraints"
+ motion:constraintSetEnd="@+id/split_constraints"
+ motion:duration="0"
+ motion:autoTransition="none" />
+
+ <!-- No changes to default layout -->
+ <ConstraintSet android:id="@+id/single_constraints" />
+
+ <ConstraintSet android:id="@+id/split_constraints">
+
+ <Constraint
+ android:id="@+id/keyguard_bouncer_message_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintEnd_toStartOf="@+id/password_center_guideline"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+ androidprv:layout_constraintVertical_chainStyle="spread_inside" />
+ <Constraint
+ android:id="@+id/passwordEntry_container"
+ android:layout_width="280dp"
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintVertical_bias="0.5"
+ androidprv:layout_constraintHorizontal_bias="0.5"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintStart_toStartOf="@+id/password_center_guideline"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintTop_toTopOf="parent"/>
+ <Constraint
+ android:id="@+id/keyguard_selector_fade_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintEnd_toStartOf="@+id/password_center_guideline"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@+id/keyguard_bouncer_message_container" />
+
+ </ConstraintSet>
+</MotionScene>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 8200e5c..9059230 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -17,6 +17,7 @@
package com.android.systemui.shared.rotation;
import static android.content.pm.PackageManager.FEATURE_PC;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
@@ -37,6 +38,8 @@
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -67,6 +70,7 @@
import java.io.PrintWriter;
import java.util.Optional;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
/**
@@ -119,6 +123,8 @@
private final int mIconCwStart0ResId;
@DrawableRes
private final int mIconCwStart90ResId;
+ /** Defaults to mainExecutor if not set via {@link #setBgExecutor(Executor)}. */
+ private Executor mBgExecutor;
@DrawableRes
private int mIconResId;
@@ -178,6 +184,8 @@
mAccessibilityManager = AccessibilityManager.getInstance(context);
mTaskStackListener = new TaskStackListenerImpl();
mWindowRotationProvider = windowRotationProvider;
+
+ mBgExecutor = context.getMainExecutor();
}
public void setRotationButton(RotationButton rotationButton,
@@ -193,6 +201,10 @@
return mContext;
}
+ public void setBgExecutor(Executor bgExecutor) {
+ mBgExecutor = bgExecutor;
+ }
+
/**
* Called during Taskbar initialization.
*/
@@ -219,8 +231,11 @@
mListenersRegistered = true;
- updateDockedState(mContext.registerReceiver(mDockedReceiver,
- new IntentFilter(Intent.ACTION_DOCK_EVENT)));
+ mBgExecutor.execute(() -> {
+ final Intent intent = mContext.registerReceiver(mDockedReceiver,
+ new IntentFilter(Intent.ACTION_DOCK_EVENT));
+ mContext.getMainExecutor().execute(() -> updateDockedState(intent));
+ });
if (registerRotationWatcher) {
try {
@@ -246,11 +261,13 @@
mListenersRegistered = false;
- try {
- mContext.unregisterReceiver(mDockedReceiver);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Docked receiver already unregistered", e);
- }
+ mBgExecutor.execute(() -> {
+ try {
+ mContext.unregisterReceiver(mDockedReceiver);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Docked receiver already unregistered", e);
+ }
+ });
if (mRotationWatcherRegistered) {
try {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 8738d33..bfd43dc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -257,7 +257,7 @@
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
mFalsingCollector, mKeyguardViewController,
- mFeatureFlags);
+ mDevicePostureController, mFeatureFlags);
} else if (keyguardInputView instanceof KeyguardPINView) {
((KeyguardPINView) keyguardInputView).setIsLockScreenLandscapeEnabled(
mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 59ee0d8..d30f497 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.WindowInsets.Type.ime;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
@@ -27,10 +28,13 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Insets;
@@ -46,12 +50,16 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.constraintlayout.motion.widget.MotionLayout;
import com.android.app.animation.Interpolators;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.TextViewInputDisabler;
import com.android.systemui.DejankUtils;
import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.DevicePostureController;
+
+
/**
* Displays an alphanumeric (latin-1) key entry for the user to enter
* an unlock password
@@ -68,10 +76,14 @@
private TextView mPasswordEntry;
private TextViewInputDisabler mPasswordEntryDisabler;
-
private Interpolator mLinearOutSlowInInterpolator;
private Interpolator mFastOutLinearInInterpolator;
private DisappearAnimationListener mDisappearAnimationListener;
+ @Nullable private MotionLayout mContainerMotionLayout;
+ private boolean mAlreadyUsingSplitBouncer = false;
+ private boolean mIsLockScreenLandscapeEnabled = false;
+ @DevicePostureController.DevicePostureInt
+ private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
private static final int[] DISABLE_STATE_SET = {-android.R.attr.state_enabled};
private static final int[] ENABLE_STATE_SET = {android.R.attr.state_enabled};
@@ -89,6 +101,21 @@
context, android.R.interpolator.fast_out_linear_in);
}
+ /**
+ * Use motion layout (new bouncer implementation) if LOCKSCREEN_ENABLE_LANDSCAPE flag is
+ * enabled
+ */
+ public void setIsLockScreenLandscapeEnabled() {
+ mIsLockScreenLandscapeEnabled = true;
+ findContainerLayout();
+ }
+
+ private void findContainerLayout() {
+ if (mIsLockScreenLandscapeEnabled) {
+ mContainerMotionLayout = findViewById(R.id.password_container);
+ }
+ }
+
@Override
protected void resetState() {
}
@@ -124,6 +151,40 @@
}
}
+ void onDevicePostureChanged(@DevicePostureController.DevicePostureInt int posture) {
+ if (mLastDevicePosture == posture) return;
+ mLastDevicePosture = posture;
+
+ if (mIsLockScreenLandscapeEnabled) {
+ boolean useSplitBouncerAfterFold =
+ mLastDevicePosture == DEVICE_POSTURE_CLOSED
+ && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+
+ if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
+ updateConstraints(useSplitBouncerAfterFold);
+ }
+ }
+
+ }
+
+ @Override
+ protected void updateConstraints(boolean useSplitBouncer) {
+ mAlreadyUsingSplitBouncer = useSplitBouncer;
+ KeyguardSecurityViewFlipper.LayoutParams params =
+ (KeyguardSecurityViewFlipper.LayoutParams) getLayoutParams();
+
+ if (useSplitBouncer) {
+ if (mContainerMotionLayout == null) return;
+ mContainerMotionLayout.jumpToState(R.id.split_constraints);
+ params.maxWidth = Integer.MAX_VALUE;
+ } else {
+ mContainerMotionLayout.jumpToState(R.id.single_constraints);
+ params.maxWidth = getResources()
+ .getDimensionPixelSize(R.dimen.keyguard_security_width);
+ }
+
+ setLayoutParams(params);
+ }
@Override
protected void onFinishInflate() {
@@ -131,6 +192,11 @@
mPasswordEntry = findViewById(getPasswordTextViewId());
mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry);
+
+ // EditText cursor can fail screenshot tests, so disable it when testing
+ if (ActivityManager.isRunningInTestHarness()) {
+ mPasswordEntry.setCursorVisible(false);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 5dbd014..ab8cd53 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+
import android.content.res.Resources;
import android.os.UserHandle;
import android.text.Editable;
@@ -41,6 +43,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.List;
@@ -49,8 +52,10 @@
extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms
-
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
+ private final DevicePostureController mPostureController;
+ private final DevicePostureController.Callback mPostureCallback = posture ->
+ mView.onDevicePostureChanged(posture);
private final InputMethodManager mInputMethodManager;
private final DelayableExecutor mMainExecutor;
private final KeyguardViewController mKeyguardViewController;
@@ -106,14 +111,19 @@
@Main Resources resources,
FalsingCollector falsingCollector,
KeyguardViewController keyguardViewController,
+ DevicePostureController postureController,
FeatureFlags featureFlags) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController, featureFlags);
mKeyguardSecurityCallback = keyguardSecurityCallback;
mInputMethodManager = inputMethodManager;
+ mPostureController = postureController;
mMainExecutor = mainExecutor;
mKeyguardViewController = keyguardViewController;
+ if (featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) {
+ view.setIsLockScreenLandscapeEnabled();
+ }
mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
@@ -127,6 +137,9 @@
mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ mView.onDevicePostureChanged(mPostureController.getDevicePosture());
+ mPostureController.addCallback(mPostureCallback);
+
// Set selected property on so the view can send accessibility events.
mPasswordEntry.setSelected(true);
mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener);
@@ -164,6 +177,7 @@
protected void onViewDetached() {
super.onViewDetached();
mPasswordEntry.setOnEditorActionListener(null);
+ mPostureController.removeCallback(mPostureCallback);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 4cc90c2..5528837 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
import android.util.Log;
@@ -156,7 +157,7 @@
switch (securityMode) {
case Pattern: return R.layout.keyguard_pattern_view;
case PIN: return R.layout.keyguard_pin_motion_layout;
- case Password: return R.layout.keyguard_password_view;
+ case Password: return R.layout.keyguard_password_motion_layout;
case SimPin: return R.layout.keyguard_sim_pin_view;
case SimPuk: return R.layout.keyguard_sim_puk_view;
default:
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index 5d732fb..cbb7e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.mediaprojection.appselector.view
import android.app.ActivityOptions
+import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
import android.app.IActivityTaskManager
import android.graphics.Rect
import android.os.Binder
@@ -129,6 +130,9 @@
view.width,
view.height
)
+ activityOptions.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
activityOptions.launchCookie = launchCookie
activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 146b5f5..79e7b71 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -359,6 +359,7 @@
public void setBackgroundExecutor(Executor bgExecutor) {
mBgExecutor = bgExecutor;
+ mRotationButtonController.setBgExecutor(bgExecutor);
}
public void setDisplayTracker(DisplayTracker displayTracker) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index e1b608f..ee67348 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.whenever
import org.junit.Before
@@ -67,6 +68,7 @@
@Mock
private lateinit var mKeyguardMessageAreaController:
KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ @Mock private lateinit var postureController: DevicePostureController
private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
@@ -89,6 +91,7 @@
`when`(keyguardPasswordView.resources).thenReturn(context.resources)
val fakeFeatureFlags = FakeFeatureFlags()
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
keyguardPasswordViewController =
KeyguardPasswordViewController(
keyguardPasswordView,
@@ -104,6 +107,7 @@
mContext.resources,
falsingCollector,
keyguardViewController,
+ postureController,
fakeFeatureFlags
)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 7c1861e..decc457 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -60,6 +60,7 @@
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.UserSwitcherController
@@ -140,6 +141,7 @@
@Mock private lateinit var userInteractor: UserInteractor
@Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var postureController: DevicePostureController
@Captor
private lateinit var swipeListenerArgumentCaptor:
@@ -197,6 +199,7 @@
featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+ featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
keyguardPasswordViewController =
KeyguardPasswordViewController(
@@ -213,6 +216,7 @@
mock(),
null,
keyguardViewController,
+ postureController,
featureFlags
)
diff --git a/services/core/java/com/android/server/SmartStorageMaintIdler.java b/services/core/java/com/android/server/SmartStorageMaintIdler.java
index 8996926..44f1e76 100644
--- a/services/core/java/com/android/server/SmartStorageMaintIdler.java
+++ b/services/core/java/com/android/server/SmartStorageMaintIdler.java
@@ -25,6 +25,7 @@
import android.util.Slog;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
public class SmartStorageMaintIdler extends JobService {
private static final String TAG = "SmartStorageMaintIdler";
@@ -34,15 +35,15 @@
private static final int SMART_MAINT_JOB_ID = 2808;
- private boolean mStarted;
+ private final AtomicBoolean mStarted = new AtomicBoolean(false);
private JobParameters mJobParams;
private final Runnable mFinishCallback = new Runnable() {
@Override
public void run() {
Slog.i(TAG, "Got smart storage maintenance service completion callback");
- if (mStarted) {
+ if (mStarted.get()) {
jobFinished(mJobParams, false);
- mStarted = false;
+ mStarted.set(false);
}
// ... and try again in a next period
scheduleSmartIdlePass(SmartStorageMaintIdler.this,
@@ -52,18 +53,26 @@
@Override
public boolean onStartJob(JobParameters params) {
- mJobParams = params;
- StorageManagerService ms = StorageManagerService.sSelf;
- if (ms != null) {
- mStarted = true;
- ms.runSmartIdleMaint(mFinishCallback);
+ final StorageManagerService ms = StorageManagerService.sSelf;
+ if (mStarted.compareAndSet(false, true)) {
+ new Thread() {
+ public void run() {
+ mJobParams = params;
+ if (ms != null) {
+ ms.runSmartIdleMaint(mFinishCallback);
+ } else {
+ mStarted.set(false);
+ }
+ }
+ }.start();
+ return ms != null;
}
- return ms != null;
+ return false;
}
@Override
public boolean onStopJob(JobParameters params) {
- mStarted = false;
+ mStarted.set(false);
return false;
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index a787644..25ca509c 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2842,7 +2842,7 @@
return true;
}
- void runSmartIdleMaint(Runnable callback) {
+ synchronized void runSmartIdleMaint(Runnable callback) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
try {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b5911f6..4c70db8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9851,6 +9851,10 @@
PriorityDump.dump(mPriorityDumper, fd, pw, args);
}
+ private static final String TICK =
+ "---------------------------------------"
+ + "----------------------------------------";
+
private void dumpEverything(FileDescriptor fd, PrintWriter pw, String[] args, int opti,
boolean dumpAll, String dumpPackage, int displayIdFilter, boolean dumpClient,
boolean dumpNormalPriority, int dumpAppId, boolean dumpProxies) {
@@ -9906,6 +9910,11 @@
sdumper.dumpLocked();
}
}
+
+ // No need to hold the lock.
+ pw.println(TICK);
+ AnrTimer.dump(pw, false);
+
// We drop the lock here because we can't call dumpWithClient() with the lock held;
// if the caller wants a consistent state for the !dumpClient case, it can call this
// method with the lock held.
@@ -10351,6 +10360,8 @@
mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
mOomAdjuster.dumpCacheOomRankerSettings(pw);
}
+ } else if ("timers".equals(cmd)) {
+ AnrTimer.dump(pw, true);
} else if ("services".equals(cmd) || "s".equals(cmd)) {
if (dumpClient) {
ActiveServices.ServiceDumper dumper;
@@ -15866,7 +15877,15 @@
activeInstr.mWatcher = watcher;
activeInstr.mUiAutomationConnection = uiAutomationConnection;
activeInstr.mResultClass = className;
- activeInstr.mHasBackgroundActivityStartsPermission = false;
+ activeInstr.mHasBackgroundActivityStartsPermission =
+ isSdkInSandbox
+ // TODO(b/261864298): consider using START_ACTIVITIES_FROM_BACKGROUND.
+ && checkPermission(
+ android.Manifest.permission
+ .START_ACTIVITIES_FROM_SDK_SANDBOX,
+ Binder.getCallingPid(),
+ Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED;
activeInstr.mHasBackgroundForegroundServiceStartsPermission = false;
// Instrumenting sdk sandbox without a restart is not supported
activeInstr.mNoRestart = false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 942d35a..a057f32 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -4095,6 +4095,7 @@
pw.println(" lru: raw LRU process list");
pw.println(" binder-proxies: stats on binder objects and IPCs");
pw.println(" settings: currently applied config settings");
+ pw.println(" timers: the current ANR timer state");
pw.println(" service [COMP_SPEC]: service client-side state");
pw.println(" package [PACKAGE_NAME]: all state related to given package");
pw.println(" all: dump all activities");
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index 7d98443..e0a2246 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -20,6 +20,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.content.pm.ApplicationInfo;
+import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
import android.util.ArraySet;
@@ -267,6 +268,7 @@
private class AnrRecord {
final ProcessRecord mApp;
final int mPid;
+ final int mUid;
final String mActivityShortComponentName;
final String mParentShortComponentName;
final TimeoutRecord mTimeoutRecord;
@@ -283,6 +285,7 @@
Future<File> firstPidFilePromise) {
mApp = anrProcess;
mPid = anrProcess.mPid;
+ mUid = anrProcess.uid;
mActivityShortComponentName = activityShortComponentName;
mParentShortComponentName = parentShortComponentName;
mTimeoutRecord = timeoutRecord;
diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java
new file mode 100644
index 0000000..cd6f009
--- /dev/null
+++ b/services/core/java/com/android/server/am/AnrTimer.java
@@ -0,0 +1,834 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.text.TextUtils.formatSimple;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.Keep;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ProcessCpuTracker;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class managers AnrTimers. An AnrTimer is a substitute for a delayed Message. In legacy
+ * mode, the timer just sends a delayed message. In modern mode, the timer is implemented in
+ * native code; on expiration, the message is sent without delay.
+ *
+ * <p>There are four external operations on a timer:
+ * <ul>
+ *
+ * <li>{@link #start} starts a timer. The timer is started with an object that the message
+ * argument. The timer is also given the pid and uid of the target. A timer that is started must
+ * be canceled, accepted, or discarded.
+ *
+ * <li>{@link #cancel} stops a timer and removes any in-flight expiration messages.
+ *
+ * <li>{@link #accept} acknowledges that the timer has expired, and that an ANR should be
+ * generated. This clears bookkeeping information for the timer.
+ *
+ * <li>{@link #discard} acknowledges that the timer has expired but, for other reasons, no ANR
+ * will be generated. This clears bookkeeping information for the timer.
+ *
+ *</li></p>
+ *
+ * <p>There is one internal operation on a timer: {@link #expire}. A timer may have automatic
+ * extensions enabled. If so, the extension is computed and if the extension is non-zero, the timer
+ * is restarted with the extension timeout. If extensions are disabled or if the extension is zero,
+ * the client process is notified of the expiration.
+ *
+ * @hide
+ */
+abstract class AnrTimer<V> {
+
+ /**
+ * The log tag.
+ */
+ final static String TAG = "AnrTimer";
+
+ /**
+ * The trace track for these events. There is a single track for all AnrTimer instances. The
+ * tracks give a sense of handler latency: the time between timer expiration and ANR
+ * collection.
+ */
+ private final static String TRACK = "AnrTimer";
+
+ /**
+ * Enable debug messages.
+ */
+ private static boolean DEBUG = false;
+
+ /**
+ * The trace tag.
+ */
+ private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+ /**
+ * Enable tracing from the time a timer expires until it is accepted or discarded. This is
+ * used to diagnose long latencies in the client.
+ */
+ private static final boolean ENABLE_TRACING = false;
+
+ /**
+ * The status of an ANR timer. TIMER_INVALID status is returned when an error is detected.
+ */
+ private static final int TIMER_INVALID = 0;
+ private static final int TIMER_RUNNING = 1;
+ private static final int TIMER_EXPIRED = 2;
+
+ @IntDef(prefix = { "TIMER_" }, value = {
+ TIMER_INVALID, TIMER_RUNNING, TIMER_EXPIRED
+ })
+ private @interface TimerStatus {}
+
+ /**
+ * A static list of all known AnrTimer instances, used for dumping and testing.
+ */
+ @GuardedBy("sAnrTimerList")
+ private static final ArrayList<WeakReference<AnrTimer>> sAnrTimerList = new ArrayList<>();
+
+ /**
+ * An error is defined by its issue, the operation that detected the error, the tag of the
+ * affected service, a short stack of the bad call, and the stringified arg associated with
+ * the error.
+ */
+ private static final class Error {
+ /** The issue is the kind of error that was detected. This is a free-form string. */
+ final String issue;
+ /** The operation that detected the error: start, cancel, accept, or discard. */
+ final String operation;
+ /** The argument (stringified) passed in to the operation. */
+ final String arg;
+ /** The tag of the associated AnrTimer. */
+ final String tag;
+ /** A partial stack that localizes the caller of the operation. */
+ final StackTraceElement[] stack;
+
+ Error(@NonNull String issue, @NonNull String operation, @NonNull String tag,
+ @NonNull StackTraceElement[] stack, @NonNull String arg) {
+ this.issue = issue;
+ this.operation = operation;
+ this.tag = tag;
+ this.stack = stack;
+ this.arg = arg;
+ }
+ }
+
+ /**
+ * A list of errors detected during processing. Errors correspond to "timer not found"
+ * conditions. The stack trace identifies the source of the call. The list is
+ * first-in/first-out, and the size is limited to MAX_SAVED_ERROR_COUNT.
+ */
+ @GuardedBy("sErrors")
+ private static final ArrayList<Error> sErrors = new ArrayList<>();
+
+ /**
+ * The maximum number of errors that are saved in the sErrors list.
+ */
+ private static final int MAX_SAVED_ERROR_COUNT = 20;
+
+ /**
+ * A record of a single anr timer. The pid and uid are retained for reference but they do not
+ * participate in the equality tests. A {@link Timer} is bound to its parent {@link AnrTimer}
+ * through the owner field. Access to timer fields is guarded by the mLock of the owner.
+ */
+ private static class Timer {
+ /** The AnrTimer that is managing this Timer. */
+ final AnrTimer owner;
+
+ /** The argument that uniquely identifies the Timer in the context of its current owner. */
+ final Object arg;
+ /** The pid of the process being tracked by this Timer. */
+ final int pid;
+ /** The uid of the process being tracked by this Timer as reported by the kernel. */
+ final int uid;
+ /** The original timeout. */
+ final long timeoutMs;
+
+ /** The status of the Timer. */
+ @GuardedBy("owner.mLock")
+ @TimerStatus
+ int status;
+
+ /** The absolute time the timer was startd */
+ final long startedMs;
+
+ /** Fields used by the native timer service. */
+
+ /** The timer ID: used to exchange information with the native service. */
+ int timerId;
+
+ /** Fields used by the legacy timer service. */
+
+ /**
+ * The process's cpu delay time when the timer starts . It is meaningful only if
+ * extendable is true. The cpu delay is cumulative, so the incremental delay that occurs
+ * during a timer is the delay at the end of the timer minus this value. Units are in
+ * milliseconds.
+ */
+ @GuardedBy("owner.mLock")
+ long initialCpuDelayMs;
+
+ /** True if the timer has been extended. */
+ @GuardedBy("owner.mLock")
+ boolean extended;
+
+ /**
+ * Fetch a new Timer. This is private. Clients should get a new timer using the obtain()
+ * method.
+ */
+ private Timer(int pid, int uid, @Nullable Object arg, long timeoutMs,
+ @NonNull AnrTimer service) {
+ this.arg = arg;
+ this.pid = pid;
+ this.uid = uid;
+ this.timerId = 0;
+ this.timeoutMs = timeoutMs;
+ this.startedMs = now();
+ this.owner = service;
+ this.initialCpuDelayMs = 0;
+ this.extended = false;
+ this.status = TIMER_INVALID;
+ }
+
+ /** Get a timer. This implementation constructs a new timer. */
+ static Timer obtain(int pid, int uid, @Nullable Object arg, long timeout,
+ @NonNull AnrTimer service) {
+ return new Timer(pid, uid, arg, timeout, service);
+ }
+
+ /** Release a timer. This implementation simply drops the timer. */
+ void release() {
+ }
+
+ /** Return the age of the timer. This is used for debugging. */
+ long age() {
+ return now() - startedMs;
+ }
+
+ /**
+ * The hash code is generated from the owner and the argument. By definition, the
+ * combination must be unique for the lifetime of an in-use Timer.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(owner, arg);
+ }
+
+ /**
+ * The equality check compares the owner and the argument. By definition, the combination
+ * must be unique for the lifetime of an in-use Timer.
+ */
+ @Override
+ public boolean equals(Object r) {
+ if (r instanceof Timer) {
+ Timer t = (Timer) r;
+ return Objects.equals(owner, t.owner) && Objects.equals(arg, t.arg);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ final int myStatus;
+ synchronized (owner.mLock) {
+ myStatus = status;
+ }
+ return "timerId=" + timerId + " pid=" + pid + " uid=" + uid
+ + " " + statusString(myStatus) + " " + owner.mLabel;
+ }
+ }
+
+ /** A lock for the AnrTimer instance. */
+ private final Object mLock = new Object();
+
+ /**
+ * The map from client argument to the associated timer.
+ */
+ @GuardedBy("mLock")
+ private final ArrayMap<V, Timer> mTimerMap = new ArrayMap<>();
+
+ /** The highwater mark of started, but not closed, timers. */
+ @GuardedBy("mLock")
+ private int mMaxStarted = 0;
+
+ /**
+ * The total number of timers started.
+ */
+ @GuardedBy("mLock")
+ private int mTotalStarted = 0;
+
+ /**
+ * The total number of errors detected.
+ */
+ @GuardedBy("mLock")
+ private int mTotalErrors = 0;
+
+ /**
+ * The total number of timers that have expired.
+ */
+ @GuardedBy("mLock")
+ private int mTotalExpired = 0;
+
+ /**
+ * A TimerService that generates a timeout event <n> milliseconds in the future. See the
+ * class documentation for an explanation of the operations.
+ */
+ private abstract class TimerService {
+ /** Start a timer. The timeout must be initialized. */
+ abstract boolean start(@NonNull Timer timer);
+
+ abstract void cancel(@NonNull Timer timer);
+
+ abstract void accept(@NonNull Timer timer);
+
+ abstract void discard(@NonNull Timer timer);
+ }
+
+ /**
+ * A class to assist testing. All methods are null by default but can be overridden as
+ * necessary for a test.
+ */
+ @VisibleForTesting
+ static class Injector {
+ /**
+ * Return a handler for the given Callback.
+ */
+ Handler getHandler(@NonNull Handler.Callback callback) {
+ return null;
+ };
+
+ /**
+ * Return a CpuTracker.
+ */
+ CpuTracker getTracker() {
+ return null;
+ }
+ }
+
+ /**
+ * A helper class to measure CPU delays. Given a process ID, this class will return the
+ * cumulative CPU delay for the PID, since process inception. This class is defined to assist
+ * testing.
+ */
+ @VisibleForTesting
+ static class CpuTracker {
+ /**
+ * The parameter to ProcessCpuTracker indicates that statistics should be collected on a
+ * single process and not on the collection of threads associated with that process.
+ */
+ private final ProcessCpuTracker mCpu = new ProcessCpuTracker(false);
+
+ /** A simple wrapper to fetch the delay. This method can be overridden for testing. */
+ long delay(int pid) {
+ return mCpu.getCpuDelayTimeForPid(pid);
+ }
+ }
+
+ /**
+ * The "user-space" implementation of the timer service. This service uses its own message
+ * handler to create timeouts.
+ */
+ private class HandlerTimerService extends TimerService {
+ /** The lock for this handler */
+ private final Object mLock = new Object();
+
+ /** The message handler for scheduling future events. */
+ private final Handler mHandler;
+
+ /** The interface to fetch process statistics that might extend an ANR timeout. */
+ private final CpuTracker mCpu;
+
+ /** Create a HandlerTimerService based on the input handler. */
+ HandlerTimerService(@NonNull Handler handler) {
+ mHandler = new Handler(handler.getLooper(), this::expires);
+ mCpu = new CpuTracker();
+ }
+
+ /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */
+ @VisibleForTesting
+ HandlerTimerService(@NonNull Injector injector) {
+ mHandler = injector.getHandler(this::expires);
+ mCpu = injector.getTracker();
+ }
+
+ /** Post a message with the specified timeout. The timer is not modified. */
+ private void post(@NonNull Timer t, long timeoutMillis) {
+ final Message msg = mHandler.obtainMessage();
+ msg.obj = t;
+ mHandler.sendMessageDelayed(msg, timeoutMillis);
+ }
+
+ /**
+ * The local expiration handler first attempts to compute a timer extension. If the timer
+ * should be extended, it is rescheduled in the future (granting more time to the
+ * associated process). If the timer should not be extended then the timeout is delivered
+ * to the client.
+ *
+ * A process is extended to account for the time the process was swapped out and was not
+ * runnable through no fault of its own. A timer can only be extended once and only if
+ * the AnrTimer permits extensions. Finally, a timer will never be extended by more than
+ * the original timeout, so the total timeout will never be more than twice the originally
+ * configured timeout.
+ */
+ private boolean expires(Message msg) {
+ Timer t = (Timer) msg.obj;
+ synchronized (mLock) {
+ long extension = 0;
+ if (mExtend && !t.extended) {
+ extension = mCpu.delay(t.pid) - t.initialCpuDelayMs;
+ if (extension < 0) extension = 0;
+ if (extension > t.timeoutMs) extension = t.timeoutMs;
+ t.extended = true;
+ }
+ if (extension > 0) {
+ post(t, extension);
+ } else {
+ onExpiredLocked(t, now());
+ }
+ }
+ return true;
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ boolean start(@NonNull Timer t) {
+ if (mExtend) {
+ t.initialCpuDelayMs = mCpu.delay(t.pid);
+ }
+ post(t, t.timeoutMs);
+ return true;
+ }
+
+ @Override
+ void cancel(@NonNull Timer t) {
+ mHandler.removeMessages(0, t);
+ }
+
+ @Override
+ void accept(@NonNull Timer t) {
+ // Nothing to do.
+ }
+
+ @Override
+ void discard(@NonNull Timer t) {
+ // Nothing to do.
+ }
+
+ /** The string identifies this subclass of AnrTimerService as being based on handlers. */
+ @Override
+ public String toString() {
+ return "handler";
+ }
+ }
+
+ /**
+ * The handler for messages sent from this instance.
+ */
+ private final Handler mHandler;
+
+ /**
+ * The message type for messages sent from this interface.
+ */
+ private final int mWhat;
+
+ /**
+ * A label that identifies the AnrTimer associated with a Timer in log messages.
+ */
+ private final String mLabel;
+
+ /**
+ * Whether this timer instance supports extending timeouts.
+ */
+ private final boolean mExtend;
+
+ /**
+ * The timer service to use for this AnrTimer.
+ */
+ private final TimerService mTimerService;
+
+ /**
+ * Whether or not canceling a non-existent timer is an error. Clients often cancel freely
+ * preemptively, without knowing if the timer was ever started. Keeping this variable true
+ * means that such behavior is not an error.
+ */
+ private final boolean mLenientCancel = true;
+
+ /**
+ * The common constructor. A null injector results in a normal, production timer.
+ */
+ @VisibleForTesting
+ AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend,
+ @Nullable Injector injector) {
+ mHandler = handler;
+ mWhat = what;
+ mLabel = label;
+ mExtend = extend;
+ if (injector == null) {
+ mTimerService = new HandlerTimerService(handler);
+ } else {
+ mTimerService = new HandlerTimerService(injector);
+ }
+ synchronized (sAnrTimerList) {
+ sAnrTimerList.add(new WeakReference(this));
+ }
+ Log.i(TAG, formatSimple("created %s label: \"%s\"", mTimerService.toString(), label));
+ }
+
+ /**
+ * Create one timer instance for production. The client can ask for extensible timeouts.
+ */
+ AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
+ this(handler, what, label, extend, null);
+ }
+
+ /**
+ * Create one timer instance for production. There are no extensible timeouts.
+ */
+ AnrTimer(@NonNull Handler handler, int what, @NonNull String label) {
+ this(handler, what, label, false, null);
+ }
+
+ /**
+ * Start a trace on the timer. The trace is laid down in the AnrTimerTrack.
+ */
+ private void traceBegin(Timer t, String what) {
+ if (ENABLE_TRACING) {
+ final String label = formatSimple("%s(%d,%d,%s)", what, t.pid, t.uid, mLabel);
+ final int cookie = t.hashCode();
+ Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie);
+ }
+ }
+
+ /**
+ * End a trace on the timer.
+ */
+ private void traceEnd(Timer t) {
+ if (ENABLE_TRACING) {
+ final int cookie = t.hashCode();
+ Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie);
+ }
+ }
+
+ /**
+ * Return the string representation for a timer status.
+ */
+ private static String statusString(int s) {
+ switch (s) {
+ case TIMER_INVALID: return "invalid";
+ case TIMER_RUNNING: return "running";
+ case TIMER_EXPIRED: return "expired";
+ }
+ return formatSimple("unknown: %d", s);
+ }
+
+ /**
+ * Delete the timer associated with arg from the maps and return it. Return null if the timer
+ * was not found.
+ */
+ @GuardedBy("mLock")
+ private Timer removeLocked(V arg) {
+ Timer timer = mTimerMap.remove(arg);
+ return timer;
+ }
+
+ /**
+ * Return the number of timers currently running.
+ */
+ @VisibleForTesting
+ static int sizeOfTimerList() {
+ synchronized (sAnrTimerList) {
+ int totalTimers = 0;
+ for (int i = 0; i < sAnrTimerList.size(); i++) {
+ AnrTimer client = sAnrTimerList.get(i).get();
+ if (client != null) totalTimers += client.mTimerMap.size();
+ }
+ return totalTimers;
+ }
+ }
+
+ /**
+ * Clear out all existing timers. This will lead to unexpected behavior if used carelessly.
+ * It is available only for testing. It returns the number of times that were actually
+ * erased.
+ */
+ @VisibleForTesting
+ static int resetTimerListForHermeticTest() {
+ synchronized (sAnrTimerList) {
+ int mapLen = 0;
+ for (int i = 0; i < sAnrTimerList.size(); i++) {
+ AnrTimer client = sAnrTimerList.get(i).get();
+ if (client != null) {
+ mapLen += client.mTimerMap.size();
+ client.mTimerMap.clear();
+ }
+ }
+ if (mapLen > 0) {
+ Log.w(TAG, formatSimple("erasing timer list: clearing %d timers", mapLen));
+ }
+ return mapLen;
+ }
+ }
+
+ /**
+ * Report something about a timer.
+ */
+ private void report(@NonNull Timer timer, @NonNull String msg) {
+ Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg));
+ }
+
+ /**
+ * Start a timer.
+ */
+ boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+ final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, this);
+ synchronized (mLock) {
+ Timer old = mTimerMap.get(arg);
+ if (old != null) {
+ // There is an existing timer. This is a protocol error in the client. Record
+ // the error and then clean up by canceling running timers and discarding expired
+ // timers.
+ restartedLocked(old.status, arg);
+ if (old.status == TIMER_EXPIRED) {
+ discard(arg);
+ } else {
+ cancel(arg);
+ }
+ }
+ if (mTimerService.start(timer)) {
+ timer.status = TIMER_RUNNING;
+ mTimerMap.put(arg, timer);
+ mTotalStarted++;
+ mMaxStarted = Math.max(mMaxStarted, mTimerMap.size());
+ if (DEBUG) report(timer, "start");
+ return true;
+ } else {
+ Log.e(TAG, "AnrTimer.start failed");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Cancel a timer. Return false if the timer was not found.
+ */
+ boolean cancel(@NonNull V arg) {
+ synchronized (mLock) {
+ Timer timer = removeLocked(arg);
+ if (timer == null) {
+ if (!mLenientCancel) notFoundLocked("cancel", arg);
+ return false;
+ }
+ mTimerService.cancel(timer);
+ // There may be an expiration message in flight. Cancel it.
+ mHandler.removeMessages(mWhat, arg);
+ if (DEBUG) report(timer, "cancel");
+ timer.release();
+ return true;
+ }
+ }
+
+ /**
+ * Accept a timer in the framework-level handler. The timeout has been accepted and the
+ * timeout handler is executing. Return false if the timer was not found.
+ */
+ boolean accept(@NonNull V arg) {
+ synchronized (mLock) {
+ Timer timer = removeLocked(arg);
+ if (timer == null) {
+ notFoundLocked("accept", arg);
+ return false;
+ }
+ mTimerService.accept(timer);
+ traceEnd(timer);
+ if (DEBUG) report(timer, "accept");
+ timer.release();
+ return true;
+ }
+ }
+
+ /**
+ * Discard a timer in the framework-level handler. For whatever reason, the timer is no
+ * longer interesting. No statistics are collected. Return false if the time was not found.
+ */
+ boolean discard(@NonNull V arg) {
+ synchronized (mLock) {
+ Timer timer = removeLocked(arg);
+ if (timer == null) {
+ notFoundLocked("discard", arg);
+ return false;
+ }
+ mTimerService.discard(timer);
+ traceEnd(timer);
+ if (DEBUG) report(timer, "discard");
+ timer.release();
+ return true;
+ }
+ }
+
+ /**
+ * The notifier that a timer has fired. The timer is not modified.
+ */
+ @GuardedBy("mLock")
+ private void onExpiredLocked(@NonNull Timer timer, long when) {
+ if (DEBUG) report(timer, "expire");
+ traceBegin(timer, "expired");
+ mHandler.sendMessage(Message.obtain(mHandler, mWhat, timer.arg));
+ synchronized (mLock) {
+ mTotalExpired++;
+ }
+ }
+
+ /**
+ * Dump a single AnrTimer.
+ */
+ private void dump(IndentingPrintWriter pw) {
+ synchronized (mLock) {
+ pw.format("timer: %s\n", mLabel);
+ pw.increaseIndent();
+ pw.format("started=%d maxStarted=%d running=%d expired=%d error=%d\n",
+ mTotalStarted, mMaxStarted, mTimerMap.size(),
+ mTotalExpired, mTotalErrors);
+ pw.decreaseIndent();
+ }
+ }
+
+ /**
+ * Enable or disable debugging.
+ */
+ static void debug(boolean f) {
+ DEBUG = f;
+ }
+
+ /**
+ * The current time in milliseconds.
+ */
+ private static long now() {
+ return SystemClock.uptimeMillis();
+ }
+
+ /**
+ * Log an error. A limited stack trace leading to the client call that triggered the error is
+ * recorded. The stack trace assumes that this method is not called directly.
+ *
+ * If DEBUG is true, a log message is generated as well.
+ */
+ @GuardedBy("mLock")
+ private void recordErrorLocked(String operation, String errorMsg, Object arg) {
+ StackTraceElement[] s = Thread.currentThread().getStackTrace();
+ final String what = Objects.toString(arg);
+ // The copy range starts at the caller of the timer operation, and includes three levels.
+ // This should be enough to isolate the location of the call.
+ StackTraceElement[] location = Arrays.copyOfRange(s, 6, 9);
+ synchronized (sErrors) {
+ // Ensure the error list does not grow beyond the limit.
+ while (sErrors.size() >= MAX_SAVED_ERROR_COUNT) {
+ sErrors.remove(0);
+ }
+ // Add the new error to the list.
+ sErrors.add(new Error(errorMsg, operation, mLabel, location, what));
+ }
+ if (DEBUG) Log.w(TAG, operation + " " + errorMsg + " " + mLabel + " timer " + what);
+ mTotalErrors++;
+ }
+
+ /**
+ * Log an error about a timer not found.
+ */
+ @GuardedBy("mLock")
+ private void notFoundLocked(String operation, Object arg) {
+ recordErrorLocked(operation, "notFound", arg);
+ }
+
+ /**
+ * Log an error about a timer that is started when there is an existing timer.
+ */
+ @GuardedBy("mLock")
+ private void restartedLocked(@TimerStatus int status, Object arg) {
+ recordErrorLocked("start", status == TIMER_EXPIRED ? "autoDiscard" : "autoCancel", arg);
+ }
+
+ /**
+ * Dump a single error to the output stream.
+ */
+ private static void dump(IndentingPrintWriter ipw, int seq, Error err) {
+ ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag,
+ err.issue, err.arg);
+ ipw.increaseIndent();
+ for (int i = 0; i < err.stack.length; i++) {
+ ipw.println(" " + err.stack[i].toString());
+ }
+ ipw.decreaseIndent();
+ }
+
+ /**
+ * Dump all errors to the output stream.
+ */
+ private static void dumpErrors(IndentingPrintWriter ipw) {
+ ArrayList<Error> errors;
+ synchronized (sErrors) {
+ if (sErrors.size() == 0) return;
+ errors = (ArrayList<Error>) sErrors.clone();
+ }
+ ipw.println("Errors");
+ ipw.increaseIndent();
+ for (int i = 0; i < errors.size(); i++) {
+ dump(ipw, i, errors.get(i));
+ }
+ ipw.decreaseIndent();
+ }
+
+ /**
+ * Dumpsys output.
+ */
+ static void dump(@NonNull PrintWriter pw, boolean verbose) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.println("AnrTimer statistics");
+ ipw.increaseIndent();
+ synchronized (sAnrTimerList) {
+ for (int i = 0; i < sAnrTimerList.size(); i++) {
+ AnrTimer client = sAnrTimerList.get(i).get();
+ if (client != null) client.dump(ipw);
+ }
+ }
+ if (verbose) dumpErrors(ipw);
+ ipw.format("AnrTimerEnd\n");
+ ipw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index fc8175b..c35a3b2 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -101,10 +101,9 @@
boolean runningOomAdjusted;
/**
- * Snapshotted value of {@link ProcessRecord#getCpuDelayTime()}, typically
- * used when deciding if we should extend the soft ANR timeout.
+ * True if a timer has been started against this queue.
*/
- long lastCpuDelayTime;
+ private boolean mTimeoutScheduled;
/**
* Snapshotted value of {@link ProcessStateRecord#getCurProcState()} before
@@ -1344,6 +1343,21 @@
return head;
}
+ /**
+ * Set the timeout flag to indicate that an ANR timer has been started. A value of true means a
+ * timer is running; a value of false means there is no timer running.
+ */
+ void setTimeoutScheduled(boolean timeoutStarted) {
+ mTimeoutScheduled = timeoutStarted;
+ }
+
+ /**
+ * Get the timeout flag
+ */
+ boolean timeoutScheduled() {
+ return mTimeoutScheduled;
+ }
+
@Override
public String toString() {
if (mCachedToString == null) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 8d2edaa..5b71595 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -149,6 +149,8 @@
// We configure runnable size only once at boot; it'd be too complex to
// try resizing dynamically at runtime
mRunning = new BroadcastProcessQueue[mConstants.getMaxRunningQueues()];
+
+ mAnrTimer = new BroadcastAnrTimer(mLocalHandler);
}
/**
@@ -242,14 +244,19 @@
*/
private @UptimeMillisLong long mLastTestFailureTime;
+ /**
+ * The ANR timer service for broadcasts.
+ */
+ @GuardedBy("mService")
+ private final BroadcastAnrTimer mAnrTimer;
+
private static final int MSG_UPDATE_RUNNING_LIST = 1;
- private static final int MSG_DELIVERY_TIMEOUT_SOFT = 2;
- private static final int MSG_DELIVERY_TIMEOUT_HARD = 3;
- private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4;
- private static final int MSG_CHECK_HEALTH = 5;
- private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6;
- private static final int MSG_PROCESS_FREEZABLE_CHANGED = 7;
- private static final int MSG_UID_STATE_CHANGED = 8;
+ private static final int MSG_DELIVERY_TIMEOUT = 2;
+ private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 3;
+ private static final int MSG_CHECK_HEALTH = 4;
+ private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 5;
+ private static final int MSG_PROCESS_FREEZABLE_CHANGED = 6;
+ private static final int MSG_UID_STATE_CHANGED = 7;
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -264,12 +271,8 @@
updateRunningList();
return true;
}
- case MSG_DELIVERY_TIMEOUT_SOFT: {
- deliveryTimeoutSoft((BroadcastProcessQueue) msg.obj, msg.arg1);
- return true;
- }
- case MSG_DELIVERY_TIMEOUT_HARD: {
- deliveryTimeoutHard((BroadcastProcessQueue) msg.obj);
+ case MSG_DELIVERY_TIMEOUT: {
+ deliveryTimeout((BroadcastProcessQueue) msg.obj);
return true;
}
case MSG_BG_ACTIVITY_START_TIMEOUT: {
@@ -1024,12 +1027,12 @@
// immediately assume delivery success
final boolean assumeDelivered = r.isAssumedDelivered(index);
if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
- queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
-
+ queue.setTimeoutScheduled(true);
final int softTimeoutMillis = (int) (r.isForeground() ? mFgConstants.TIMEOUT
: mBgConstants.TIMEOUT);
- mLocalHandler.sendMessageDelayed(Message.obtain(mLocalHandler,
- MSG_DELIVERY_TIMEOUT_SOFT, softTimeoutMillis, 0, queue), softTimeoutMillis);
+ mAnrTimer.start(queue, softTimeoutMillis);
+ } else {
+ queue.setTimeoutScheduled(false);
}
if (r.mBackgroundStartPrivileges.allowsAny()) {
@@ -1107,7 +1110,7 @@
// If we were trying to deliver a manifest broadcast, throw the error as we need
// to try redelivering the broadcast to this receiver.
if (receiver instanceof ResolveInfo) {
- mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
+ mAnrTimer.cancel(queue);
throw new BroadcastDeliveryFailedException(e);
}
finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
@@ -1156,41 +1159,29 @@
r.resultTo = null;
}
- private void deliveryTimeoutSoft(@NonNull BroadcastProcessQueue queue,
- int softTimeoutMillis) {
+ private void deliveryTimeout(@NonNull BroadcastProcessQueue queue) {
synchronized (mService) {
- deliveryTimeoutSoftLocked(queue, softTimeoutMillis);
+ deliveryTimeoutLocked(queue);
}
}
- private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
- int softTimeoutMillis) {
- if (queue.app != null) {
- // Instead of immediately triggering an ANR, extend the timeout by
- // the amount of time the process was runnable-but-waiting; we're
- // only willing to do this once before triggering an hard ANR
- final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime;
- final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis);
- mLocalHandler.sendMessageDelayed(
- Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue),
- hardTimeoutMillis);
- } else {
- deliveryTimeoutHardLocked(queue);
- }
- }
-
- private void deliveryTimeoutHard(@NonNull BroadcastProcessQueue queue) {
- synchronized (mService) {
- deliveryTimeoutHardLocked(queue);
- }
- }
-
- private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
+ private void deliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) {
finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
- "deliveryTimeoutHardLocked");
+ "deliveryTimeoutLocked");
demoteFromRunningLocked(queue);
}
+ private class BroadcastAnrTimer extends AnrTimer<BroadcastProcessQueue> {
+ BroadcastAnrTimer(Handler handler) {
+ super(Objects.requireNonNull(handler),
+ MSG_DELIVERY_TIMEOUT, "BROADCAST_TIMEOUT", true);
+ }
+
+ void start(@NonNull BroadcastProcessQueue queue, long timeoutMillis) {
+ start(queue, queue.app.getPid(), queue.app.uid, timeoutMillis);
+ }
+ }
+
@Override
public boolean finishReceiverLocked(@NonNull ProcessRecord app, int resultCode,
@Nullable String resultData, @Nullable Bundle resultExtras, boolean resultAbort,
@@ -1292,14 +1283,16 @@
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
r.anrCount++;
if (app != null && !app.isDebugging()) {
+ mAnrTimer.accept(queue);
final String packageName = getReceiverPackageName(receiver);
final String className = getReceiverClassName(receiver);
mService.appNotResponding(queue.app,
TimeoutRecord.forBroadcastReceiver(r.intent, packageName, className));
+ } else {
+ mAnrTimer.discard(queue);
}
- } else {
- mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
- mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
+ } else if (queue.timeoutScheduled()) {
+ mAnrTimer.cancel(queue);
}
// Given that a receiver just finished, check if the "waitingFor" conditions are met.
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 69e4a5b..515c7fb 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -951,6 +951,9 @@
throw new SecurityException("Media projections require a foreground service"
+ " of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION");
}
+
+ mCallback = callback;
+ registerCallback(mCallback);
try {
mToken = callback.asBinder();
mDeathEater = () -> {
@@ -995,11 +998,6 @@
}
}
startProjectionLocked(this);
-
- // Register new callbacks after stop has been dispatched to previous session.
- mCallback = callback;
- registerCallback(mCallback);
-
// Mark this token as used when the app gets the MediaProjection instance.
mCountStarts++;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6a5b9d8..bada816 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2826,9 +2826,6 @@
mAssistants.onBootPhaseAppsCanStart();
mConditionProviders.onBootPhaseAppsCanStart();
mHistoryManager.onBootPhaseAppsCanStart();
- if (expireBitmaps()) {
- NotificationBitmapJobService.scheduleJob(getContext());
- }
registerDeviceConfigChange();
migrateDefaultNAS();
maybeShowInitialReviewPermissionsNotification();
@@ -2837,6 +2834,10 @@
} else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
mPreferencesHelper.updateFixedImportance(mUm.getUsers());
mPreferencesHelper.migrateNotificationPermissions(mUm.getUsers());
+ } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+ if (expireBitmaps()) {
+ NotificationBitmapJobService.scheduleJob(getContext());
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2ee0706..e1e5e6d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -44,6 +44,7 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
+
import static com.android.server.pm.DexOptHelper.useArtService;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
@@ -111,6 +112,7 @@
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.DataLoaderType;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
@@ -1142,15 +1144,18 @@
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final ParsedPackage parsedPackage;
+ final ArchivedPackageParcel archivedPackage;
try (PackageParser2 pp = mPm.mInjector.getPreparingPackageParser()) {
if (request.getPackageLite() == null || !request.isArchived()) {
// TODO: pass packageLite from install request instead of reparsing the package
parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
+ archivedPackage = null;
} else {
// Archived install mode, no APK.
parsedPackage = pp.parsePackageFromPackageLite(request.getPackageLite(),
parseFlags);
+ archivedPackage = request.getPackageLite().getArchivedPackage();
}
} catch (PackageManagerException e) {
throw new PrepareFailure("Failed parse during installPackageLI", e);
@@ -1833,7 +1838,7 @@
shouldCloseFreezerBeforeReturn = false;
request.setPrepareResult(replace, targetScanFlags, targetParseFlags,
- oldPackageState, parsedPackage,
+ oldPackageState, parsedPackage, archivedPackage,
replace /* clearCodeCache */, sysPkg, ps, disabledPs);
} finally {
request.setFreezer(freezer);
@@ -2164,6 +2169,9 @@
}
}
if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
+ mPm.createArchiveStateIfNeeded(ps,
+ installRequest.getArchivedPackage(),
+ installRequest.getNewUsers());
mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
mPm.updateInstantAppInstallerLocked(packageName);
}
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 3a6d423..ff347ac 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -21,6 +21,7 @@
import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.os.Process.INVALID_UID;
+
import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
import static com.android.server.pm.PackageManagerService.TAG;
@@ -29,6 +30,7 @@
import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.app.AppOpsManager;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.DataLoaderType;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.PackageInstaller;
@@ -77,6 +79,8 @@
/** parsed package to be scanned */
@Nullable
private ParsedPackage mParsedPackage;
+ @Nullable
+ private ArchivedPackageParcel mArchivedPackage;
private boolean mClearCodeCache;
private boolean mSystem;
@Nullable
@@ -189,6 +193,7 @@
}
mInstallArgs = null;
mParsedPackage = parsedPackage;
+ mArchivedPackage = null;
mParseFlags = parseFlags;
mScanFlags = scanFlags;
mScanResult = scanResult;
@@ -448,6 +453,9 @@
return mParsedPackage;
}
+ @Nullable
+ public ArchivedPackageParcel getArchivedPackage() { return mArchivedPackage; }
+
@ParsingPackageUtils.ParseFlags
public int getParseFlags() {
return mParseFlags;
@@ -753,7 +761,8 @@
public void setPrepareResult(boolean replace, int scanFlags,
int parseFlags, PackageState existingPackageState,
- ParsedPackage packageToScan, boolean clearCodeCache, boolean system,
+ ParsedPackage packageToScan, ArchivedPackageParcel archivedPackage,
+ boolean clearCodeCache, boolean system,
PackageSetting originalPs, PackageSetting disabledPs) {
mReplace = replace;
mScanFlags = scanFlags;
@@ -761,6 +770,7 @@
mExistingPackageName =
existingPackageState != null ? existingPackageState.getPackageName() : null;
mParsedPackage = packageToScan;
+ mArchivedPackage = archivedPackage;
mClearCodeCache = clearCodeCache;
mSystem = system;
mOriginalPs = originalPs;
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 64cdca3..551b1ae 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -31,12 +31,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.ArchivedActivityParcel;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -56,6 +59,7 @@
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserStateInternal;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -158,7 +162,8 @@
String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
verifyInstaller(responsibleInstallerPackage);
- List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps, userId);
+ List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(),
+ userId);
final CompletableFuture<ArchiveState> archiveState = new CompletableFuture<>();
mPm.mHandler.post(() -> {
try {
@@ -172,13 +177,34 @@
return archiveState;
}
- private ArchiveState createArchiveStateInternal(String packageName, int userId,
+ static ArchiveState createArchiveState(@NonNull ArchivedPackageParcel archivedPackage,
+ int userId, String installerPackage) {
+ try {
+ var packageName = archivedPackage.packageName;
+ var mainActivities = archivedPackage.archivedActivities;
+ List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.length);
+ for (int i = 0, size = mainActivities.length; i < size; ++i) {
+ var mainActivity = mainActivities[i];
+ Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i);
+ ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
+ mainActivity.title, iconPath, null);
+ archiveActivityInfos.add(activityInfo);
+ }
+
+ return new ArchiveState(archiveActivityInfos, installerPackage);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to create archive state", e);
+ return null;
+ }
+ }
+
+ ArchiveState createArchiveStateInternal(String packageName, int userId,
List<LauncherActivityInfo> mainActivities, String installerPackage)
throws IOException {
- List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>();
- for (int i = 0; i < mainActivities.size(); i++) {
+ List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
+ for (int i = 0, size = mainActivities.size(); i < size; i++) {
LauncherActivityInfo mainActivity = mainActivities.get(i);
- Path iconPath = storeIcon(packageName, mainActivity, userId);
+ Path iconPath = storeIcon(packageName, mainActivity, userId, i);
ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
mainActivity.getLabel().toString(), iconPath, null);
archiveActivityInfos.add(activityInfo);
@@ -188,17 +214,30 @@
}
// TODO(b/298452477) Handle monochrome icons.
+ private static Path storeIconForParcel(String packageName, ArchivedActivityParcel mainActivity,
+ @UserIdInt int userId, int index) throws IOException {
+ if (mainActivity.iconBitmap == null) {
+ return null;
+ }
+ File iconsDir = createIconsDir(userId);
+ File iconFile = new File(iconsDir, packageName + "-" + index + ".png");
+ try (FileOutputStream out = new FileOutputStream(iconFile)) {
+ out.write(mainActivity.iconBitmap);
+ out.flush();
+ }
+ return iconFile.toPath();
+ }
+
@VisibleForTesting
Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
- @UserIdInt int userId)
- throws IOException {
+ @UserIdInt int userId, int index) throws IOException {
int iconResourceId = mainActivity.getActivityInfo().getIconResource();
if (iconResourceId == 0) {
// The app doesn't define an icon. No need to store anything.
return null;
}
File iconsDir = createIconsDir(userId);
- File iconFile = new File(iconsDir, packageName + "-" + mainActivity.getName() + ".png");
+ File iconFile = new File(iconsDir, packageName + "-" + index + ".png");
Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0));
try (FileOutputStream out = new FileOutputStream(iconFile)) {
// Note: Quality is ignored for PNGs.
@@ -228,6 +267,9 @@
*/
public boolean verifySupportsUnarchival(String installerPackage) {
// TODO(b/278553670) Check if installerPackage supports unarchival.
+ if (TextUtils.isEmpty(installerPackage)) {
+ return false;
+ }
return true;
}
@@ -310,16 +352,16 @@
/* initialExtras= */ null);
}
- private List<LauncherActivityInfo> getLauncherActivityInfos(PackageStateInternal ps,
+ List<LauncherActivityInfo> getLauncherActivityInfos(String packageName,
int userId) throws PackageManager.NameNotFoundException {
List<LauncherActivityInfo> mainActivities =
Binder.withCleanCallingIdentity(() -> getLauncherApps().getActivityList(
- ps.getPackageName(),
+ packageName,
new UserHandle(userId)));
if (mainActivities.isEmpty()) {
throw new PackageManager.NameNotFoundException(
TextUtils.formatSimple("The app %s does not have a main activity.",
- ps.getPackageName()));
+ packageName));
}
return mainActivities;
@@ -340,7 +382,7 @@
return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS;
}
- private String getResponsibleInstallerPackage(PackageStateInternal ps) {
+ static String getResponsibleInstallerPackage(PackageStateInternal ps) {
return TextUtils.isEmpty(ps.getInstallSource().mUpdateOwnerPackageName)
? ps.getInstallSource().mInstallerPackageName
: ps.getInstallSource().mUpdateOwnerPackageName;
@@ -423,7 +465,7 @@
}
}
- private File createIconsDir(@UserIdInt int userId) throws IOException {
+ private static File createIconsDir(@UserIdInt int userId) throws IOException {
File iconsDir = getIconsDir(userId);
if (!iconsDir.isDirectory()) {
iconsDir.delete();
@@ -436,7 +478,7 @@
return iconsDir;
}
- private File getIconsDir(int userId) {
+ private static File getIconsDir(int userId) {
return new File(Environment.getDataSystemCeDirectory(userId), ARCHIVE_ICONS_DIR);
}
@@ -462,4 +504,90 @@
drawable.draw(canvas);
return bitmap;
}
+
+ private static byte[] bytesFromBitmapFile(Path path) throws IOException {
+ if (path == null) {
+ return null;
+ }
+ // Technically we could just read the bytes, but we want to be sure we store the
+ // right format.
+ return bytesFromBitmap(BitmapFactory.decodeFile(path.toString()));
+ }
+
+ private static byte[] bytesFromBitmap(Bitmap bitmap) throws IOException {
+ if (bitmap == null) {
+ return null;
+ }
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream(
+ bitmap.getByteCount())) {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
+ return baos.toByteArray();
+ }
+ }
+
+ /**
+ * Creates serializable archived activities from existing ArchiveState.
+ */
+ static ArchivedActivityParcel[] createArchivedActivities(ArchiveState archiveState)
+ throws IOException {
+ var infos = archiveState.getActivityInfos();
+ if (infos == null || infos.isEmpty()) {
+ throw new IllegalArgumentException("No activities in archive state");
+ }
+
+ List<ArchivedActivityParcel> activities = new ArrayList<>(infos.size());
+ for (int i = 0, size = infos.size(); i < size; ++i) {
+ var info = infos.get(i);
+ if (info == null) {
+ continue;
+ }
+ var archivedActivity = new ArchivedActivityParcel();
+ archivedActivity.title = info.getTitle();
+ archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap());
+ archivedActivity.monochromeIconBitmap = bytesFromBitmapFile(
+ info.getMonochromeIconBitmap());
+ activities.add(archivedActivity);
+ }
+
+ if (activities.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Failed to extract title and icon of main activities");
+ }
+
+ return activities.toArray(new ArchivedActivityParcel[activities.size()]);
+ }
+
+ /**
+ * Creates serializable archived activities from launcher activities.
+ */
+ static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos)
+ throws IOException {
+ if (infos == null || infos.isEmpty()) {
+ throw new IllegalArgumentException("No launcher activities");
+ }
+
+ List<ArchivedActivityParcel> activities = new ArrayList<>(infos.size());
+ for (int i = 0, size = infos.size(); i < size; ++i) {
+ var info = infos.get(i);
+ if (info == null) {
+ continue;
+ }
+ var archivedActivity = new ArchivedActivityParcel();
+ archivedActivity.title = info.getLabel().toString();
+ archivedActivity.iconBitmap =
+ info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap(
+ drawableToBitmap(info.getIcon(/* density= */ 0)));
+ // TODO(b/298452477) Handle monochrome icons.
+ archivedActivity.monochromeIconBitmap = null;
+ activities.add(archivedActivity);
+ }
+
+ if (activities.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Failed to extract title and icon of main activities");
+ }
+
+ return activities.toArray(new ArchivedActivityParcel[activities.size()]);
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2e9da09..512d338 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -40,6 +40,7 @@
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.O_WRONLY;
+
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.util.XmlUtils.readBitmapAttribute;
import static com.android.internal.util.XmlUtils.readByteArrayAttribute;
@@ -1165,11 +1166,6 @@
throw new IllegalArgumentException(
"Archived installation can only use Streaming System DataLoader.");
}
- if (!TextUtils.isEmpty(params.appPackageName) && !isArchivedInstallationAllowed(
- params.appPackageName)) {
- throw new IllegalArgumentException(
- "Archived installation of this package is not allowed.");
- }
}
}
@@ -1548,6 +1544,10 @@
}
var archPkg = metadata.getArchivedPackage();
+ if (archPkg == null) {
+ throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
+ "Metadata does not contain ArchivedPackage: " + file);
+ }
if (archPkg.packageName == null || archPkg.signingDetails == null) {
throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
"ArchivedPackage does not contain required info: " + file);
@@ -3395,8 +3395,7 @@
"Archived installation of this package is not allowed.");
}
- if (!isInstalledByAdb(getInstallSource().mInitiatingPackageName)
- && !mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival(
+ if (!mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival(
getInstallSource().mInstallerPackageName)) {
throw new PackageManagerException(
PackageManager.INSTALL_FAILED_SESSION_INVALID,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d23dcbc..b5b6ce0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -36,6 +36,7 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
+
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
import static com.android.server.pm.DexOptHelper.useArtService;
@@ -116,11 +117,7 @@
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.overlay.OverlayPaths;
-import android.content.pm.parsing.ApkLite;
-import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -228,6 +225,7 @@
import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserState;
@@ -1438,6 +1436,92 @@
return extras;
}
+ ArchivedPackageParcel getArchivedPackageInternal(@NonNull String packageName, int userId) {
+ Objects.requireNonNull(packageName);
+ int binderUid = Binder.getCallingUid();
+
+ Computer snapshot = snapshotComputer();
+ snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
+ "getArchivedPackage");
+
+ ArchivedPackageParcel archPkg = new ArchivedPackageParcel();
+ archPkg.packageName = packageName;
+
+ ArchiveState archiveState;
+ synchronized (mLock) {
+ PackageSetting ps = mSettings.getPackageLPr(packageName);
+ if (ps == null) {
+ return null;
+ }
+ var psi = ps.getUserStateOrDefault(userId);
+ archiveState = psi.getArchiveState();
+ if (archiveState == null && !psi.isInstalled()) {
+ return null;
+ }
+
+ archPkg.signingDetails = ps.getSigningDetails();
+
+ long longVersionCode = ps.getVersionCode();
+ archPkg.versionCodeMajor = (int) (longVersionCode >> 32);
+ archPkg.versionCode = (int) longVersionCode;
+
+ // TODO(b/297916136): extract target sdk version.
+ archPkg.targetSdkVersion = MIN_INSTALLABLE_TARGET_SDK;
+
+ // These get translated in flags important for user data management.
+ archPkg.defaultToDeviceProtectedStorage = String.valueOf(
+ ps.isDefaultToDeviceProtectedStorage());
+ archPkg.requestLegacyExternalStorage = String.valueOf(
+ ps.isRequestLegacyExternalStorage());
+ archPkg.userDataFragile = String.valueOf(ps.isUserDataFragile());
+ }
+
+ try {
+ if (archiveState != null) {
+ archPkg.archivedActivities = PackageArchiver.createArchivedActivities(
+ archiveState);
+ } else {
+ var mainActivities =
+ mInstallerService.mPackageArchiver.getLauncherActivityInfos(packageName,
+ userId);
+ archPkg.archivedActivities = PackageArchiver.createArchivedActivities(
+ mainActivities);
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Package does not have a main activity", e);
+ }
+
+ return archPkg;
+ }
+
+ void createArchiveStateIfNeeded(PackageSetting pkgSetting, ArchivedPackageParcel archivePackage,
+ int[] userIds) {
+ if (pkgSetting == null || archivePackage == null
+ || archivePackage.archivedActivities == null || userIds == null
+ || userIds.length == 0) {
+ return;
+ }
+
+ String responsibleInstallerPackage = PackageArchiver.getResponsibleInstallerPackage(
+ pkgSetting);
+ // TODO(b/278553670) Check if responsibleInstallerPackage supports unarchival.
+ if (TextUtils.isEmpty(responsibleInstallerPackage)) {
+ Slog.e(TAG, "Can't create archive state: responsible installer is empty");
+ return;
+ }
+ for (int userId : userIds) {
+ var archiveState = PackageArchiver.createArchiveState(archivePackage, userId,
+ responsibleInstallerPackage);
+ if (archiveState == null) {
+ continue;
+ }
+ pkgSetting
+ .modifyUserState(userId)
+ .setArchiveState(archiveState);
+ }
+ }
+
+
void scheduleWriteSettings() {
// We normally invalidate when we write settings, but in cases where we delay and
// coalesce settings writes, this strategy would have us invalidate the cache too late.
@@ -6301,35 +6385,8 @@
}
@Override
- public ArchivedPackageParcel getArchivedPackage(String apkPath) {
- ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
- ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(),
- new File(apkPath), ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
- if (result.isError()) {
- throw new IllegalArgumentException(result.getErrorMessage(), result.getException());
- }
- final ApkLite apk = result.getResult();
-
- ArchivedPackageParcel archPkg = new ArchivedPackageParcel();
- archPkg.packageName = apk.getPackageName();
- archPkg.signingDetails = apk.getSigningDetails();
-
- archPkg.versionCodeMajor = apk.getVersionCodeMajor();
- archPkg.versionCode = apk.getVersionCode();
-
- archPkg.targetSdkVersion = apk.getTargetSdkVersion();
-
- // These get translated in flags important for user data management.
- archPkg.backupAllowed = String.valueOf(apk.isBackupAllowed());
- archPkg.defaultToDeviceProtectedStorage = String.valueOf(
- apk.isDefaultToDeviceProtectedStorage());
- archPkg.requestLegacyExternalStorage = String.valueOf(
- apk.isRequestLegacyExternalStorage());
- archPkg.userDataFragile = String.valueOf(apk.isUserDataFragile());
- archPkg.clearUserDataOnFailedRestoreAllowed = String.valueOf(
- apk.isClearUserDataOnFailedRestoreAllowed());
-
- return archPkg;
+ public ArchivedPackageParcel getArchivedPackage(@NonNull String packageName, int userId) {
+ return getArchivedPackageInternal(packageName, userId);
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 8d82085..1b30c4b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -26,6 +26,7 @@
import static android.content.pm.PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
import static android.content.pm.PackageManager.RESTRICTION_HIDE_NOTIFICATIONS;
import static android.content.pm.PackageManager.RESTRICTION_NONE;
+
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -82,9 +83,9 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
-import android.os.Environment;
import android.os.IBinder;
import android.os.IUserManager;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.os.PersistableBundle;
@@ -130,6 +131,7 @@
import libcore.io.IoUtils;
import libcore.io.Streams;
+import libcore.util.HexEncoding;
import java.io.BufferedReader;
import java.io.File;
@@ -248,8 +250,6 @@
return runStreamingInstall();
case "install-incremental":
return runIncrementalInstall();
- case "install-archived":
- return runArchivedInstall();
case "install-abandon":
case "install-destroy":
return runInstallAbandon();
@@ -277,6 +277,10 @@
return runUninstall();
case "clear":
return runClear();
+ case "get-archived-package-metadata":
+ return runGetArchivedPackageMetadata();
+ case "install-archived":
+ return runArchivedInstall();
case "enable":
return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
case "disable":
@@ -1808,6 +1812,56 @@
return doRemoveSplits(sessionId, splitNames, true /*logSuccess*/);
}
+ private int runGetArchivedPackageMetadata() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ int userId = UserHandle.USER_CURRENT;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ final String packageName = getNextArg();
+ if (packageName == null) {
+ pw.println("Error: package name not specified");
+ return 1;
+ }
+ final int translatedUserId = translateUserId(userId, UserHandle.USER_NULL,
+ "runGetArchivedPackageMetadata");
+
+ try {
+ var archivedPackage = mInterface.getArchivedPackage(packageName, translatedUserId);
+ if (archivedPackage == null) {
+ pw.write("Package not found " + packageName);
+ return -1;
+ }
+
+ Parcel parcel = Parcel.obtain();
+ byte[] bytes;
+ try {
+ parcel.writeParcelable(archivedPackage, 0);
+ bytes = parcel.marshall();
+ } finally {
+ parcel.recycle();
+ }
+
+ String encoded = HexEncoding.encodeToString(bytes);
+ pw.write(encoded);
+ } catch (Exception e) {
+ getErrPrintWriter().println("Failed to get archived package, reason: " + e);
+ pw.println("Failure [failed to get archived package], reason: " + e);
+ return -1;
+ }
+ return 0;
+ }
+
private int runInstallExisting() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
int userId = UserHandle.USER_CURRENT;
@@ -4146,28 +4200,24 @@
throw new IllegalArgumentException("Error: Can't open file: " + inPath);
}
- File tmpFile = null;
+ final String encoded;
final ParcelFileDescriptor fd = fdWithSize.first;
+ final int size = (int) (long) fdWithSize.second;
try (InputStream inStream = new AutoCloseInputStream(fd)) {
- final long identity = Binder.clearCallingIdentity();
- try {
- File tmpStagingDir = Environment.getDataAppDirectory(null);
- tmpFile = new File(tmpStagingDir, "tmdl" + RANDOM.nextInt() + ".tmp");
-
- try (OutputStream outStream = new FileOutputStream(tmpFile)) {
- Streams.copy(inStream, outStream);
- }
-
- return mInterface.getArchivedPackage(tmpFile.getAbsolutePath());
- } finally {
- if (tmpFile != null) {
- tmpFile.delete();
- }
- Binder.restoreCallingIdentity(identity);
- }
+ byte[] bytes = new byte[size];
+ Streams.readFully(inStream, bytes);
+ encoded = new String(bytes);
} catch (IOException e) {
- throw new IllegalArgumentException("Error: Can't stage file: " + inPath, e);
+ throw new IllegalArgumentException("Error: Can't load archived package from: " + inPath,
+ e);
}
+
+ var result = Metadata.readArchivedPackageParcel(HexEncoding.decode(encoded));
+ if (result == null) {
+ throw new IllegalArgumentException(
+ "Error: Can't parse archived package from: " + inPath);
+ }
+ return result;
}
private void processArgForLocalFile(String arg, PackageInstaller.Session session,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index fbe5a51..9e7f043 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -166,16 +166,7 @@
/** @hide */
@VisibleForTesting
public static Metadata forArchived(ArchivedPackageParcel archivedPackage) {
- Parcel parcel = Parcel.obtain();
- byte[] bytes;
- try {
- parcel.writeParcelable(archivedPackage, 0);
- bytes = parcel.marshall();
- } finally {
- parcel.recycle();
- }
-
- return new Metadata(ARCHIVED, bytes, null);
+ return new Metadata(ARCHIVED, writeArchivedPackageParcel(archivedPackage), null);
}
static Metadata forDataOnlyStreaming(String fileId) {
@@ -270,11 +261,14 @@
if (getMode() != ARCHIVED) {
throw new IllegalStateException("Not an archived package metadata.");
}
+ return readArchivedPackageParcel(this.mData);
+ }
+ static ArchivedPackageParcel readArchivedPackageParcel(byte[] bytes) {
Parcel parcel = Parcel.obtain();
ArchivedPackageParcel result;
try {
- parcel.unmarshall(this.mData, 0, this.mData.length);
+ parcel.unmarshall(bytes, 0, bytes.length);
parcel.setDataPosition(0);
result = parcel.readParcelable(ArchivedPackageParcel.class.getClassLoader());
} finally {
@@ -282,6 +276,16 @@
}
return result;
}
+
+ static byte[] writeArchivedPackageParcel(ArchivedPackageParcel archivedPackage) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeParcelable(archivedPackage, 0);
+ return parcel.marshall();
+ } finally {
+ parcel.recycle();
+ }
+ }
}
private static class DataLoader implements DataLoaderService.DataLoader {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 1884caf..2e60064 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -654,6 +654,16 @@
return (getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0;
}
+ public boolean isRequestLegacyExternalStorage() {
+ return (getPrivateFlags() & ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE)
+ != 0;
+ }
+
+ public boolean isUserDataFragile() {
+ return (getPrivateFlags() & ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA)
+ != 0;
+ }
+
public SigningDetails getSigningDetails() {
return signatures.mSigningDetails;
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 812e228..f14941b 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -28,6 +28,7 @@
import static android.os.Build.VERSION_CODES.DONUT;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
import android.annotation.AnyRes;
@@ -509,24 +510,33 @@
/* Set the global "on SD card" flag */
.setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
+ var archivedPackage = lite.getArchivedPackage();
+ if (archivedPackage == null) {
+ return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "archivePackage is missing");
+ }
+
// parseBaseAppBasicFlags
pkg
// Default true
- .setBackupAllowed(lite.isBackupAllowed())
+ .setBackupAllowed(true)
.setClearUserDataAllowed(true)
- .setClearUserDataOnFailedRestoreAllowed(
- lite.isClearUserDataOnFailedRestoreAllowed())
+ .setClearUserDataOnFailedRestoreAllowed(true)
.setAllowNativeHeapPointerTagging(true)
.setEnabled(true)
.setExtractNativeLibrariesRequested(true)
// targetSdkVersion gated
.setAllowAudioPlaybackCapture(targetSdk >= Build.VERSION_CODES.Q)
.setHardwareAccelerated(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
- .setRequestLegacyExternalStorage(lite.isRequestLegacyExternalStorage())
+ .setRequestLegacyExternalStorage(
+ XmlUtils.convertValueToBoolean(archivedPackage.requestLegacyExternalStorage,
+ targetSdk < Build.VERSION_CODES.Q))
.setCleartextTrafficAllowed(targetSdk < Build.VERSION_CODES.P)
// Default false
- .setDefaultToDeviceProtectedStorage(lite.isDefaultToDeviceProtectedStorage())
- .setUserDataFragile(lite.isUserDataFragile())
+ .setDefaultToDeviceProtectedStorage(XmlUtils.convertValueToBoolean(
+ archivedPackage.defaultToDeviceProtectedStorage, false))
+ .setUserDataFragile(
+ XmlUtils.convertValueToBoolean(archivedPackage.userDataFragile, false))
// Ints
.setCategory(ApplicationInfo.CATEGORY_UNDEFINED)
// Floats Default 0f
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3c2d0fc..00992a0 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -256,6 +256,15 @@
|| event != CLOSE_WRITE // includes the MOVED_TO case
|| wallpaper.imageWallpaperPending;
+ if (isMigration) {
+ // When separate lock screen engine is supported, migration will be handled by
+ // WallpaperDestinationChangeHandler.
+ return;
+ }
+ if (!(sysWallpaperChanged || lockWallpaperChanged)) {
+ return;
+ }
+
if (DEBUG) {
Slog.v(TAG, "Wallpaper file change: evt=" + event
+ " path=" + path
@@ -270,15 +279,6 @@
+ " needsUpdate=" + needsUpdate);
}
- if (isMigration) {
- // When separate lock screen engine is supported, migration will be handled by
- // WallpaperDestinationChangeHandler.
- return;
- }
- if (!(sysWallpaperChanged || lockWallpaperChanged)) {
- return;
- }
-
int notifyColorsWhich = 0;
synchronized (mLock) {
notifyCallbacksLocked(wallpaper);
@@ -3494,15 +3494,24 @@
}
}
+ /**
+ * Determines if the given component name is the default component. Note: a null name can be
+ * used to represent the default component.
+ * @param name The component name to check.
+ * @return True if the component name matches the default wallpaper component.
+ */
+ private boolean isDefaultComponent(ComponentName name) {
+ return name == null || name.equals(mDefaultWallpaperComponent);
+ }
+
private boolean changingToSame(ComponentName componentName, WallpaperData wallpaper) {
if (wallpaper.connection != null) {
- if (wallpaper.wallpaperComponent == null) {
- if (componentName == null) {
- if (DEBUG) Slog.v(TAG, "changingToSame: still using default");
- // Still using default wallpaper.
- return true;
- }
- } else if (wallpaper.wallpaperComponent.equals(componentName)) {
+ final ComponentName wallpaperName = wallpaper.wallpaperComponent;
+ if (isDefaultComponent(componentName) && isDefaultComponent(wallpaperName)) {
+ if (DEBUG) Slog.v(TAG, "changingToSame: still using default");
+ // Still using default wallpaper.
+ return true;
+ } else if (wallpaperName != null && wallpaperName.equals(componentName)) {
// Changing to same wallpaper.
if (DEBUG) Slog.v(TAG, "same wallpaper");
return true;
@@ -3519,6 +3528,9 @@
// Has the component changed?
if (!force && changingToSame(componentName, wallpaper)) {
try {
+ if (DEBUG_LIVE) {
+ Slog.v(TAG, "Changing to the same component, ignoring");
+ }
if (reply != null) reply.sendResult(null);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to send callback", e);
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index a5b1132..25c42b4 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -33,9 +33,7 @@
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
@@ -240,11 +238,6 @@
getInterceptorInfo(null /* clearOptionsAnimation */);
for (int i = 0; i < callbacks.size(); i++) {
- final int orderId = callbacks.keyAt(i);
- if (!shouldInterceptActivityLaunch(orderId, interceptorInfo)) {
- continue;
- }
-
final ActivityInterceptorCallback callback = callbacks.valueAt(i);
final ActivityInterceptResult interceptResult = callback.onInterceptActivityLaunch(
interceptorInfo);
@@ -543,11 +536,6 @@
ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo(
r::clearOptionsAnimationForSiblings);
for (int i = 0; i < callbacks.size(); i++) {
- final int orderId = callbacks.keyAt(i);
- if (!shouldNotifyOnActivityLaunch(orderId, info)) {
- continue;
- }
-
final ActivityInterceptorCallback callback = callbacks.valueAt(i);
callback.onActivityLaunched(taskInfo, r.info, info);
}
@@ -565,21 +553,4 @@
.build();
}
- private boolean shouldInterceptActivityLaunch(
- @ActivityInterceptorCallback.OrderedId int orderId,
- @NonNull ActivityInterceptorCallback.ActivityInterceptorInfo info) {
- if (orderId == MAINLINE_SDK_SANDBOX_ORDER_ID) {
- return info.getIntent() != null && info.getIntent().isSandboxActivity(mServiceContext);
- }
- return true;
- }
-
- private boolean shouldNotifyOnActivityLaunch(
- @ActivityInterceptorCallback.OrderedId int orderId,
- @NonNull ActivityInterceptorCallback.ActivityInterceptorInfo info) {
- if (orderId == MAINLINE_SDK_SANDBOX_ORDER_ID) {
- return info.getIntent() != null && info.getIntent().isSandboxActivity(mServiceContext);
- }
- return true;
- }
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index d8c684f..4979274 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -96,6 +96,9 @@
private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER =
"enable_credential_manager";
+ private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
+ "enable_credential_description_api";
+
private final Context mContext;
/** Cache of system service list per user id. */
@@ -321,7 +324,14 @@
}
public static boolean isCredentialDescriptionApiEnabled() {
- return true;
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API,
+ false);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
}
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
@@ -954,7 +964,7 @@
Slog.i(TAG, "registerCredentialDescription with callingPackage: " + callingPackage);
if (!isCredentialDescriptionApiEnabled()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Feature not supported");
}
enforceCallingPackage(callingPackage, Binder.getCallingUid());
@@ -974,7 +984,7 @@
if (!isCredentialDescriptionApiEnabled()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Feature not supported");
}
enforceCallingPackage(callingPackage, Binder.getCallingUid());
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index d988063..5555c19 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -18,7 +18,9 @@
import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -161,7 +163,7 @@
mArchiveManager = spy(new PackageArchiver(mContext, pm));
doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE),
- any(LauncherActivityInfo.class), eq(mUserId));
+ any(LauncherActivityInfo.class), eq(mUserId), anyInt());
}
@Test
@@ -249,7 +251,7 @@
public void archiveApp_storeIconFails() throws IntentSender.SendIntentException, IOException {
IOException e = new IOException("IO");
doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
- any(LauncherActivityInfo.class), eq(mUserId));
+ any(LauncherActivityInfo.class), eq(mUserId), anyInt());
mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
new file mode 100644
index 0000000..9fdbdda
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.Presubmit;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:AnrTimerTest
+ */
+@SmallTest
+@Presubmit
+public class AnrTimerTest {
+
+ /**
+ * A handler that allows control over when to dispatch messages and callbacks. Because most
+ * Handler methods are final, the only thing this handler can intercept is sending messages.
+ * This handler allows unit tests to be written without a need to sleep (which leads to flaky
+ * tests).
+ *
+ * This code was cloned from {@link com.android.systemui.utils.os.FakeHandler}.
+ */
+ static class TestHandler extends Handler {
+
+ private boolean mImmediate = true;
+ private ArrayList<Message> mQueuedMessages = new ArrayList<>();
+
+ ArrayList<Long> mDelays = new ArrayList<>();
+
+ TestHandler(Looper looper, Callback callback, boolean immediate) {
+ super(looper, callback);
+ mImmediate = immediate;
+ }
+
+ TestHandler(Looper looper, Callback callback) {
+ this(looper, callback, true);
+ }
+
+ /**
+ * Override sendMessageAtTime. In immediate mode, the message is immediately dispatched.
+ * In non-immediate mode, the message is enqueued to the real handler. In both cases, the
+ * original delay is computed by comparing the target dispatch time with 'now'. This
+ * computation is prone to errors if the code experiences delays. The computed time is
+ * captured in the mDelays list.
+ */
+ @Override
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ long delay = uptimeMillis - SystemClock.uptimeMillis();
+ mDelays.add(delay);
+ if (mImmediate) {
+ mQueuedMessages.add(msg);
+ dispatchQueuedMessages();
+ } else {
+ super.sendMessageAtTime(msg, uptimeMillis);
+ }
+ return true;
+ }
+
+ void setImmediate(boolean immediate) {
+ mImmediate = immediate;
+ }
+
+ /** Dispatch any messages that have been queued on the calling thread. */
+ void dispatchQueuedMessages() {
+ ArrayList<Message> messages = new ArrayList<>(mQueuedMessages);
+ mQueuedMessages.clear();
+ for (Message msg : messages) {
+ dispatchMessage(msg);
+ }
+ }
+
+ /**
+ * Compare the captured delays with the input array. The comparison is fuzzy because the
+ * captured delay (see sendMessageAtTime) is affected by process delays.
+ */
+ void verifyDelays(long[] r) {
+ final long FUZZ = 10;
+ assertEquals(r.length, mDelays.size());
+ for (int i = 0; i < mDelays.size(); i++) {
+ long t = r[i];
+ long v = mDelays.get(i);
+ assertTrue(v >= t - FUZZ && v <= t + FUZZ);
+ }
+ }
+ }
+
+ private Handler mHandler;
+ private CountDownLatch mLatch = null;
+ private ArrayList<Message> mMessages;
+
+ // The commonly used message timeout key.
+ private static final int MSG_TIMEOUT = 1;
+
+ @Before
+ public void setUp() {
+ mHandler = new Handler(Looper.getMainLooper(), this::expirationHandler);
+ mMessages = new ArrayList<>();
+ mLatch = new CountDownLatch(1);
+ AnrTimer.resetTimerListForHermeticTest();
+ }
+
+ @After
+ public void tearDown() {
+ mHandler = null;
+ mMessages = null;
+ }
+
+ // When a timer expires, set the expiration time in the message and add it to the queue.
+ private boolean expirationHandler(Message msg) {
+ mMessages.add(Message.obtain(msg));
+ mLatch.countDown();
+ return false;
+ }
+
+ // The test argument includes a pid and uid, and a tag. The tag is used to distinguish
+ // different message instances.
+ private static class TestArg {
+ final int pid;
+ final int uid;
+ final int tag;
+
+ TestArg(int pid, int uid, int tag) {
+ this.pid = pid;
+ this.uid = uid;
+ this.tag = tag;
+ }
+ @Override
+ public String toString() {
+ return String.format("pid=%d uid=%d tag=%d", pid, uid, tag);
+ }
+ }
+
+ /**
+ * An instrumented AnrTimer.
+ */
+ private class TestAnrTimer extends AnrTimer {
+ // A local copy of 'what'. The field in AnrTimer is private.
+ final int mWhat;
+
+ TestAnrTimer(Handler h, int key, String tag) {
+ super(h, key, tag);
+ mWhat = key;
+ }
+
+ TestAnrTimer() {
+ this(mHandler, MSG_TIMEOUT, caller());
+ }
+
+ TestAnrTimer(Handler h, int key, String tag, boolean extend, TestInjector injector) {
+ super(h, key, tag, extend, injector);
+ mWhat = key;
+ }
+
+ TestAnrTimer(boolean extend, TestInjector injector) {
+ this(mHandler, MSG_TIMEOUT, caller(), extend, injector);
+ }
+
+ // Return the name of method that called the constructor, assuming that this function is
+ // called from inside the constructor. The calling method is used to name the AnrTimer
+ // instance so that logs are easier to understand.
+ private static String caller() {
+ final int n = 4;
+ StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+ if (stack.length < n+1) return "test";
+ return stack[n].getMethodName();
+ }
+
+ boolean start(TestArg arg, long millis) {
+ return start(arg, arg.pid, arg.uid, millis);
+ }
+
+ int what() {
+ return mWhat;
+ }
+ }
+
+ private static class TestTracker extends AnrTimer.CpuTracker {
+ long index = 0;
+ final int skip;
+ TestTracker(int skip) {
+ this.skip = skip;
+ }
+ long delay(int pid) {
+ return index++ * skip;
+ }
+ }
+
+ private class TestInjector extends AnrTimer.Injector {
+ final boolean mImmediate;
+ final AnrTimer.CpuTracker mTracker;
+ TestHandler mTestHandler;
+
+ TestInjector(int skip, boolean immediate) {
+ mTracker = new TestTracker(skip);
+ mImmediate = immediate;
+ }
+
+ TestInjector(int skip) {
+ this(skip, true);
+ }
+
+ @Override
+ Handler getHandler(Handler.Callback callback) {
+ if (mTestHandler == null) {
+ mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate);
+ }
+ return mTestHandler;
+ }
+
+ /** Fetch the allocated handle. This does not check for nulls. */
+ TestHandler getHandler() {
+ return mTestHandler;
+ }
+
+ AnrTimer.CpuTracker getTracker() {
+ return mTracker;
+ }
+ }
+
+ // Tests
+ // 1. Start a timer and wait for expiration.
+ // 2. Start a timer and cancel it. Verify no expiration.
+ // 3. Start a timer. Shortly thereafter, restart it. Verify only one expiration.
+ // 4. Start a couple of timers. Verify max active timers. Discard one and verify the active
+ // count drops by 1. Accept one and verify the active count drops by 1.
+
+
+ @Test
+ public void testSimpleTimeout() throws Exception {
+ // Create an immediate TestHandler.
+ TestInjector injector = new TestInjector(0);
+ TestAnrTimer timer = new TestAnrTimer(false, injector);
+ TestArg t = new TestArg(1, 1, 3);
+ assertTrue(timer.start(t, 10));
+ // Delivery is immediate but occurs on a different thread.
+ assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
+ assertEquals(1, mMessages.size());
+ Message m = mMessages.get(0);
+ assertEquals(timer.what(), m.what);
+ assertEquals(t, m.obj);
+
+ // Verify that the timer is still present.
+ assertEquals(1, AnrTimer.sizeOfTimerList());
+ assertTrue(timer.accept(t));
+ assertEquals(0, AnrTimer.sizeOfTimerList());
+
+ // Verify that the timer no longer exists.
+ assertFalse(timer.accept(t));
+ }
+
+ @Test
+ public void testCancel() throws Exception {
+ // Create an non-immediate TestHandler.
+ TestInjector injector = new TestInjector(0, false);
+ TestAnrTimer timer = new TestAnrTimer(false, injector);
+
+ Handler handler = injector.getHandler();
+ assertNotNull(handler);
+ assertTrue(handler instanceof TestHandler);
+
+ // The tests that follow check for a 'what' of 0 (zero), which is the message key used
+ // by AnrTimer internally.
+ TestArg t = new TestArg(1, 1, 3);
+ assertFalse(handler.hasMessages(0));
+ assertTrue(timer.start(t, 100));
+ assertTrue(handler.hasMessages(0));
+ assertTrue(timer.cancel(t));
+ assertFalse(handler.hasMessages(0));
+
+ // Verify that no expiration messages were delivered.
+ assertEquals(0, mMessages.size());
+ assertEquals(0, AnrTimer.sizeOfTimerList());
+ }
+
+ @Test
+ public void testRestart() throws Exception {
+ // Create an non-immediate TestHandler.
+ TestInjector injector = new TestInjector(0, false);
+ TestAnrTimer timer = new TestAnrTimer(false, injector);
+
+ TestArg t = new TestArg(1, 1, 3);
+ assertTrue(timer.start(t, 2500));
+ assertTrue(timer.start(t, 1000));
+
+ // Verify that the test handler saw two timeouts.
+ injector.getHandler().verifyDelays(new long[] { 2500, 1000 });
+
+ // Verify that there is a single timer. Then cancel it.
+ assertEquals(1, AnrTimer.sizeOfTimerList());
+ assertTrue(timer.cancel(t));
+ assertEquals(0, AnrTimer.sizeOfTimerList());
+ }
+
+ @Test
+ public void testExtendNormal() throws Exception {
+ // Create an immediate TestHandler.
+ TestInjector injector = new TestInjector(5);
+ TestAnrTimer timer = new TestAnrTimer(true, injector);
+ TestArg t = new TestArg(1, 1, 3);
+ assertTrue(timer.start(t, 10));
+
+ assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
+ assertEquals(1, mMessages.size());
+ Message m = mMessages.get(0);
+ assertEquals(timer.what(), m.what);
+ assertEquals(t, m.obj);
+
+ // Verify that the test handler saw two timeouts: one of 10ms and one of 5ms.
+ injector.getHandler().verifyDelays(new long[] { 10, 5 });
+
+ // Verify that the timer is still present. Then remove it and verify that the list is
+ // empty.
+ assertEquals(1, AnrTimer.sizeOfTimerList());
+ assertTrue(timer.accept(t));
+ assertEquals(0, AnrTimer.sizeOfTimerList());
+ }
+
+ @Test
+ public void testExtendOversize() throws Exception {
+ // Create an immediate TestHandler.
+ TestInjector injector = new TestInjector(25);
+ TestAnrTimer timer = new TestAnrTimer(true, injector);
+ TestArg t = new TestArg(1, 1, 3);
+ assertTrue(timer.start(t, 10));
+
+ assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
+ assertEquals(1, mMessages.size());
+ Message m = mMessages.get(0);
+ assertEquals(timer.what(), m.what);
+ assertEquals(t, m.obj);
+
+ // Verify that the test handler saw two timeouts: one of 10ms and one of 10ms.
+ injector.getHandler().verifyDelays(new long[] { 10, 10 });
+
+ // Verify that the timer is still present. Then remove it and verify that the list is
+ // empty.
+ assertEquals(1, AnrTimer.sizeOfTimerList());
+ assertTrue(timer.accept(t));
+ assertEquals(0, AnrTimer.sizeOfTimerList());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index b0fd606..ddd1221 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -202,24 +202,6 @@
}
@Test
- public void testCreateProjection_priorProjectionGrant() throws NameNotFoundException {
- // Create a first projection.
- MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
- FakeIMediaProjectionCallback callback1 = new FakeIMediaProjectionCallback();
- projection.start(callback1);
-
- // Create a second projection.
- MediaProjectionManagerService.MediaProjection secondProjection =
- startProjectionPreconditions();
- FakeIMediaProjectionCallback callback2 = new FakeIMediaProjectionCallback();
- secondProjection.start(callback2);
-
- // Check that the second projection's callback hasn't been stopped.
- assertThat(callback1.mStopped).isTrue();
- assertThat(callback2.mStopped).isFalse();
- }
-
- @Test
public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
throws NameNotFoundException {
// Create a first projection.
@@ -803,11 +785,8 @@
}
private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub {
- boolean mStopped = false;
-
@Override
public void onStop() throws RemoteException {
- mStopped = true;
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 0989db4..568471d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
@@ -26,7 +25,6 @@
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.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -36,7 +34,6 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,13 +41,11 @@
import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManagerInternal;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
@@ -76,7 +71,6 @@
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
/**
@@ -509,88 +503,4 @@
verify(callback, times(1)).onActivityLaunched(any(), any(), any());
}
-
- @Test
- public void testSandboxServiceInterceptionHappensToIntentWithSandboxActivityAction() {
- ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
- mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
- PackageManager packageManagerMock = mock(PackageManager.class);
- String sandboxPackageNameMock = "com.sandbox.mock";
- when(mContext.getPackageManager()).thenReturn(packageManagerMock);
- when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
- Intent intent = new Intent().setAction(ACTION_START_SANDBOXED_ACTIVITY);
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- verify(spyCallback, times(1)).onInterceptActivityLaunch(
- any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
- }
-
- @Test
- public void testSandboxServiceInterceptionHappensToIntentWithSandboxPackage() {
- ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
- mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
- PackageManager packageManagerMock = mock(PackageManager.class);
- String sandboxPackageNameMock = "com.sandbox.mock";
- when(mContext.getPackageManager()).thenReturn(packageManagerMock);
- when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
- Intent intent = new Intent().setPackage(sandboxPackageNameMock);
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- verify(spyCallback, times(1)).onInterceptActivityLaunch(
- any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
- }
-
- @Test
- public void testSandboxServiceInterceptionHappensToIntentWithComponentNameWithSandboxPackage() {
- ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
- mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
- PackageManager packageManagerMock = mock(PackageManager.class);
- String sandboxPackageNameMock = "com.sandbox.mock";
- when(mContext.getPackageManager()).thenReturn(packageManagerMock);
- when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
- Intent intent = new Intent().setComponent(new ComponentName(sandboxPackageNameMock, ""));
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- verify(spyCallback, times(1)).onInterceptActivityLaunch(
- any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
- }
-
- @Test
- public void testSandboxServiceInterceptionNotCalledWhenIntentNotRelatedToSandbox() {
- ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
- mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
- PackageManager packageManagerMock = mock(PackageManager.class);
- String sandboxPackageNameMock = "com.sandbox.mock";
- when(mContext.getPackageManager()).thenReturn(packageManagerMock);
- when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
- // Intent: null
- mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null);
-
- // Action: null, Package: null, ComponentName: null
- Intent intent = new Intent();
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- // Wrong Action
- intent = new Intent().setAction(Intent.ACTION_VIEW);
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- // Wrong Package
- intent = new Intent().setPackage("Random");
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- // Wrong ComponentName's package
- intent = new Intent().setComponent(new ComponentName("Random", ""));
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- verify(spyCallback, never()).onInterceptActivityLaunch(
- any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
- }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 3d78a1d..0a70a5f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -27,9 +27,9 @@
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH;
-import android.app.AppOpsManager;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.content.ComponentName;
@@ -50,6 +50,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SharedMemory;
+import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetectionServiceFailure;
@@ -114,6 +115,9 @@
private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
+ private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED =
+ SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false);
+
/**
* Indicates the {@link HotwordDetectionService} is created.
*/
@@ -680,7 +684,8 @@
mIntent = intent;
mDetectionServiceType = detectionServiceType;
int flags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0;
- if (mVisualQueryDetectionComponentName != null
+ if (SYSPROP_VISUAL_QUERY_SERVICE_ENABLED
+ && mVisualQueryDetectionComponentName != null
&& mHotwordDetectionComponentName != null) {
flags |= Context.BIND_SHARED_ISOLATED_PROCESS;
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 471acc1..6ba77da 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -57,6 +57,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SharedMemory;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.service.voice.HotwordDetector;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
@@ -96,6 +97,8 @@
/** The delay time for retrying to request DirectActions. */
private static final long REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS = 200;
+ private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED =
+ SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false);
final boolean mValid;
@@ -715,7 +718,7 @@
} else {
verifyDetectorForVisualQueryDetectionLocked(sharedMemory);
}
- if (!verifyProcessSharingLocked()) {
+ if (SYSPROP_VISUAL_QUERY_SERVICE_ENABLED && !verifyProcessSharingLocked()) {
Slog.w(TAG, "Sandboxed detection service not in shared isolated process");
throw new IllegalStateException("VisualQueryDetectionService or HotworDetectionService "
+ "not in a shared isolated process. Please make sure to set "
@@ -914,6 +917,7 @@
if (hotwordInfo == null || visualQueryInfo == null) {
return true;
}
+ // Enforce shared isolated option is used when VisualQueryDetectionservice is enabled
return (hotwordInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0
&& (visualQueryInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0;
}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index a72f780..1f32c97 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -412,6 +412,15 @@
"android.telecom.extra.CALL_CREATED_TIME_MILLIS";
/**
+ * Optional extra for incoming containing a long which specifies the time the
+ * call was answered by user. This value is in milliseconds.
+ * @hide
+ */
+ public static final String EXTRA_CALL_ANSWERED_TIME_MILLIS =
+ "android.telecom.extra.CALL_ANSWERED_TIME_MILLIS";
+
+
+ /**
* Optional extra for incoming and outgoing calls containing a long which specifies the Epoch
* time the call was created.
* @hide
diff --git a/tools/aapt2/OWNERS b/tools/aapt2/OWNERS
index 4f655e5..55bab7d 100644
--- a/tools/aapt2/OWNERS
+++ b/tools/aapt2/OWNERS
@@ -1,4 +1,4 @@
set noparent
-toddke@google.com
zyy@google.com
-patb@google.com
\ No newline at end of file
+patb@google.com
+markpun@google.com