Merge "Fix b/265632782: Battery usage app list changed when entering the app details page and back"
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml
index 5befa1f..424d784 100644
--- a/apct-tests/perftests/multiuser/AndroidManifest.xml
+++ b/apct-tests/perftests/multiuser/AndroidManifest.xml
@@ -15,8 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.perftests.multiuser">
-
+ package="com.android.perftests.multiuser">
<uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
@@ -27,6 +26,7 @@
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.SET_WALLPAPER" />
<application>
<uses-library android:name="android.test.runner" />
@@ -38,5 +38,4 @@
<queries>
<package android:name="perftests.multiuser.apps.dummyapp" />
</queries>
-
</manifest>
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index b24076a..64ba6d1 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -15,7 +15,11 @@
*/
package android.multiuser;
+import static android.app.WallpaperManager.FLAG_LOCK;
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.annotation.NonNull;
@@ -25,6 +29,7 @@
import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.WaitResult;
+import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -34,6 +39,7 @@
import android.content.pm.IPackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IProgressListener;
@@ -59,6 +65,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -114,6 +121,7 @@
private ActivityManager mAm;
private IActivityManager mIam;
private PackageManager mPm;
+ private WallpaperManager mWm;
private ArrayList<Integer> mUsersToRemove;
private boolean mHasManagedUserFeature;
private BroadcastWaiter mBroadcastWaiter;
@@ -132,6 +140,7 @@
mIam = ActivityManager.getService();
mUsersToRemove = new ArrayList<>();
mPm = context.getPackageManager();
+ mWm = WallpaperManager.getInstance(context);
mHasManagedUserFeature = mPm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
mBroadcastWaiter = new BroadcastWaiter(context, TAG, TIMEOUT_IN_SECOND,
Intent.ACTION_USER_STARTED,
@@ -241,24 +250,27 @@
/**
* Tests starting an uninitialized user, with wait times in between iterations.
- * Measures the time until ACTION_USER_STARTED is received.
+ * Measures the time until the ProgressListener callback.
*/
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void startUser_realistic() throws RemoteException {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createUserNoFlags();
+ final ProgressWaiter waiter = new ProgressWaiter();
waitForBroadcastIdle();
- runThenWaitForBroadcasts(userId, () -> {
- mRunner.resumeTiming();
- Log.i(TAG, "Starting timer");
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
- mIam.startUserInBackground(userId);
- }, Intent.ACTION_USER_STARTED);
+ final boolean success = mIam.startUserInBackgroundWithListener(userId, waiter)
+ && waiter.waitForFinish(TIMEOUT_IN_SECOND * 1000);
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
+
+ assertTrue("Error: could not start user " + userId, success);
+
removeUser(userId);
waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
@@ -372,6 +384,32 @@
removeUser(testUser);
}
+ /** Tests switching to a previously-started, but no-longer-running, user with wait
+ * times between iterations and using a static wallpaper */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void switchUser_stopped_staticWallpaper() throws RemoteException {
+ assumeTrue(mWm.isWallpaperSupported() && mWm.isSetWallpaperAllowed());
+ final int startUser = ActivityManager.getCurrentUser();
+ final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true,
+ /* useStaticWallpaper */true);
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ waitCoolDownPeriod();
+ Log.d(TAG, "Starting timer");
+ mRunner.resumeTiming();
+
+ switchUser(testUser);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ switchUserNoCheck(startUser);
+ stopUserAfterWaitingForBroadcastIdle(testUser, true);
+ attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
+ mRunner.resumeTimingForNextIteration();
+ }
+ removeUser(testUser);
+ }
+
/** Tests switching to an already-created already-running non-owner background user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void switchUser_running() throws RemoteException {
@@ -415,6 +453,31 @@
removeUser(testUser);
}
+ /** Tests switching to an already-created already-running non-owner background user, with wait
+ * times between iterations and using a default static wallpaper */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void switchUser_running_staticWallpaper() throws RemoteException {
+ assumeTrue(mWm.isWallpaperSupported() && mWm.isSetWallpaperAllowed());
+ final int startUser = ActivityManager.getCurrentUser();
+ final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false,
+ /* useStaticWallpaper */ true);
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ waitCoolDownPeriod();
+ Log.d(TAG, "Starting timer");
+ mRunner.resumeTiming();
+
+ switchUser(testUser);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ waitForBroadcastIdle();
+ switchUserNoCheck(startUser);
+ mRunner.resumeTimingForNextIteration();
+ }
+ removeUser(testUser);
+ }
+
/** Tests stopping a background user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void stopUser() throws RemoteException {
@@ -843,14 +906,20 @@
waitForLatch("Failed to properly stop user " + userId, latch);
}
+ private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws RemoteException {
+ return initializeNewUserAndSwitchBack(stopNewUser, /* useStaticWallpaper */ false);
+ }
+
/**
* Creates a user and waits for its ACTION_USER_UNLOCKED.
* Then switches to back to the original user and waits for its switchUser() to finish.
*
* @param stopNewUser whether to stop the new user after switching to otherUser.
+ * @param useStaticWallpaper whether to switch the wallpaper of the default user to a static.
* @return userId of the newly created user.
*/
- private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws RemoteException {
+ private int initializeNewUserAndSwitchBack(boolean stopNewUser, boolean useStaticWallpaper)
+ throws RemoteException {
final int origUser = mAm.getCurrentUser();
// First, create and switch to testUser, waiting for its ACTION_USER_UNLOCKED
final int testUser = createUserNoFlags();
@@ -858,6 +927,17 @@
mAm.switchUser(testUser);
}, Intent.ACTION_USER_UNLOCKED, Intent.ACTION_MEDIA_MOUNTED);
+ if (useStaticWallpaper) {
+ assertTrue(mWm.isWallpaperSupported() && mWm.isSetWallpaperAllowed());
+ try {
+ Bitmap blank = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+ mWm.setBitmap(blank, /* visibleCropHint */ null, /* allowBackup */ true,
+ /* which */ FLAG_SYSTEM | FLAG_LOCK, testUser);
+ } catch (IOException exception) {
+ fail("Unable to set static wallpaper.");
+ }
+ }
+
// Second, switch back to origUser, waiting merely for switchUser() to finish
switchUser(origUser);
attestTrue("Didn't switch back to user, " + origUser, origUser == mAm.getCurrentUser());
diff --git a/core/api/current.txt b/core/api/current.txt
index bec2559..4b1335e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -40530,11 +40530,12 @@
method public void onLaunchVoiceAssistFromKeyguard();
method public void onPrepareToShowSession(@NonNull android.os.Bundle, int);
method public void onReady();
- method public void onShowSessionFailed();
+ method public void onShowSessionFailed(@NonNull android.os.Bundle);
method public void onShutdown();
method public void setDisabledShowContext(int);
method public final void setUiHints(@NonNull android.os.Bundle);
method public void showSession(android.os.Bundle, int);
+ field public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
field public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
field public static final String SERVICE_META_DATA = "android.voice_interaction";
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 684b271..0fce784 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10403,6 +10403,7 @@
method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public void setBootUser(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean someUserHasAccount(@NonNull String, @NonNull String);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 07750a5..8c64e40 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -528,6 +528,7 @@
method @NonNull public static String operationSafetyReasonToString(int);
method @NonNull public static String operationToString(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void resetDefaultCrossProfileIntentFilters(int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState();
method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, int);
@@ -897,6 +898,7 @@
method public boolean isDemo();
method public boolean isEnabled();
method public boolean isEphemeral();
+ method public boolean isForTesting();
method public boolean isFull();
method public boolean isGuest();
method public boolean isInitialized();
@@ -2025,6 +2027,7 @@
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createProfileForUser(@Nullable String, @NonNull String, int, int, @Nullable String[]);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getBootUser();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index dbdee07..821a23c 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -16,6 +16,8 @@
package android.accounts;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
import android.annotation.BroadcastBehavior;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -891,15 +893,24 @@
* @return An {@link AccountManagerFuture} which resolves to a Boolean, true if the account
* exists and has all of the specified features.
*/
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS_FULL)
public AccountManagerFuture<Boolean> hasFeatures(final Account account,
final String[] features,
AccountManagerCallback<Boolean> callback, Handler handler) {
+ return hasFeaturesAsUser(account, features, callback, handler, mContext.getUserId());
+ }
+
+ private AccountManagerFuture<Boolean> hasFeaturesAsUser(
+ final Account account, final String[] features,
+ AccountManagerCallback<Boolean> callback, Handler handler, int userId) {
if (account == null) throw new IllegalArgumentException("account is null");
if (features == null) throw new IllegalArgumentException("features is null");
return new Future2Task<Boolean>(handler, callback) {
@Override
public void doWork() throws RemoteException {
- mService.hasFeatures(mResponse, account, features, mContext.getOpPackageName());
+ mService.hasFeatures(
+ mResponse, account, features, userId, mContext.getOpPackageName());
}
@Override
public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
@@ -3319,7 +3330,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
public AccountManagerFuture<Bundle> finishSessionAsUser(
final Bundle sessionBundle,
final Activity activity,
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index a3a7b0c..08fb308 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -38,7 +38,7 @@
Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName);
Account[] getAccountsAsUser(String accountType, int userId, String opPackageName);
void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features,
- String opPackageName);
+ int userId, String opPackageName);
void getAccountByTypeAndFeatures(in IAccountManagerResponse response, String accountType,
in String[] features, String opPackageName);
void getAccountsByFeatures(in IAccountManagerResponse response, String accountType,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1633073..33721a0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.QUERY_ADMIN_POLICY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
@@ -4186,7 +4187,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
public boolean packageHasActiveAdmins(String packageName) {
return packageHasActiveAdmins(packageName, myUserId());
}
@@ -8743,7 +8744,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(allOf = {
android.Manifest.permission.MANAGE_DEVICE_ADMINS,
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ INTERACT_ACROSS_USERS_FULL
})
public void setActiveAdmin(@NonNull ComponentName policyReceiver, boolean refreshing,
int userHandle) {
@@ -10654,7 +10655,7 @@
*/
@UserHandleAware
@RequiresPermission(allOf = {
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ INTERACT_ACROSS_USERS_FULL,
android.Manifest.permission.MANAGE_USERS
}, conditional = true)
public @Nullable List<String> getPermittedInputMethods() {
@@ -14845,7 +14846,7 @@
* @hide
*/
@RequiresPermission(anyOf = {
- permission.INTERACT_ACROSS_USERS_FULL,
+ INTERACT_ACROSS_USERS_FULL,
permission.INTERACT_ACROSS_USERS
}, conditional = true)
public boolean isPackageAllowedToAccessCalendar(@NonNull String packageName) {
@@ -14877,7 +14878,7 @@
* @hide
*/
@RequiresPermission(anyOf = {
- permission.INTERACT_ACROSS_USERS_FULL,
+ INTERACT_ACROSS_USERS_FULL,
permission.INTERACT_ACROSS_USERS
})
public @Nullable Set<String> getCrossProfileCalendarPackages() {
@@ -14970,7 +14971,7 @@
* @hide
*/
@RequiresPermission(anyOf = {
- permission.INTERACT_ACROSS_USERS_FULL,
+ INTERACT_ACROSS_USERS_FULL,
permission.INTERACT_ACROSS_USERS,
permission.INTERACT_ACROSS_PROFILES
})
@@ -16154,6 +16155,23 @@
}
/**
+ * Reset cache for {@link #shouldAllowBypassingDevicePolicyManagementRoleQualification}.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS)
+ public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState() {
+ if (mService != null) {
+ try {
+ mService.resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* @return {@code true} if bypassing the device policy management role qualification is allowed
* with the current state of the device.
*
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 20695ca..aebeaf0 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -575,6 +575,7 @@
void resetStrings(in List<String> stringIds);
ParcelableResource getString(String stringId);
+ void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState();
boolean shouldAllowBypassingDevicePolicyManagementRoleQualification();
List<UserHandle> getPolicyManagedProfiles(in UserHandle userHandle);
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 2be0323..44747fa 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -171,6 +171,16 @@
public static final int FLAG_MAIN = 0x00004000;
/**
+ * Indicates that this user was created for the purposes of testing.
+ *
+ * <p>These users are subject to removal during tests and should not be used on actual devices
+ * used by humans.
+ *
+ * @hide
+ */
+ public static final int FLAG_FOR_TESTING = 0x00008000;
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = "FLAG_", value = {
@@ -188,7 +198,8 @@
FLAG_SYSTEM,
FLAG_PROFILE,
FLAG_EPHEMERAL_ON_CREATE,
- FLAG_MAIN
+ FLAG_MAIN,
+ FLAG_FOR_TESTING
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserInfoFlag {
@@ -369,6 +380,12 @@
return (flags & FLAG_EPHEMERAL) == FLAG_EPHEMERAL;
}
+ /** @hide */
+ @TestApi
+ public boolean isForTesting() {
+ return (flags & FLAG_FOR_TESTING) == FLAG_FOR_TESTING;
+ }
+
public boolean isInitialized() {
return (flags & FLAG_INITIALIZED) == FLAG_INITIALIZED;
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 3b4e8cd..d1d3315 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -142,4 +142,8 @@
long getUserStartRealtime();
long getUserUnlockRealtime();
boolean setUserEphemeral(int userId, boolean enableEphemeral);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS})")
+ void setBootUser(int userId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS})")
+ int getBootUser();
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index b016c781..9db1233 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5671,6 +5671,40 @@
}
}
+ /**
+ * Sets the user who should be in the foreground when boot completes. This should be called
+ * during boot, and the provided user must be a full user (i.e. not a profile).
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public void setBootUser(@NonNull UserHandle bootUser) {
+ try {
+ mService.setBootUser(bootUser.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the user who should be in the foreground when boot completes.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ @SuppressWarnings("[AndroidFrameworkContextUserId]")
+ public @NonNull UserHandle getBootUser() {
+ try {
+ return UserHandle.of(mService.getBootUser());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
/* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */
private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props";
diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl
index efae5c1..6a54606 100644
--- a/core/java/android/service/voice/IVoiceInteractionService.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionService.aidl
@@ -31,5 +31,5 @@
void getActiveServiceSupportedActions(in List<String> voiceActions,
in IVoiceActionCheckCallback callback);
void prepareToShowSession(in Bundle args, int flags);
- void showSessionFailed();
+ void showSessionFailed(in Bundle args);
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index a5156ef..df739e3 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -95,6 +95,20 @@
public static final String SERVICE_META_DATA = "android.voice_interaction";
/**
+ * Bundle key used to specify the id when the system prepares to show session. It increases for
+ * each request.
+ * <p>
+ * Type: int
+ * </p>
+ * @see #showSession(Bundle, int)
+ * @see #onPrepareToShowSession(Bundle, int)
+ * @see #onShowSessionFailed(Bundle)
+ * @see VoiceInteractionSession#onShow(Bundle, int)
+ * @see VoiceInteractionSession#show(Bundle, int)
+ */
+ public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
+
+ /**
* For apps targeting Build.VERSION_CODES.TRAMISU and above, implementors of this
* service can create multiple AlwaysOnHotwordDetector instances in parallel. They will
* also e ale to create a single SoftwareHotwordDetector in parallel with any other
@@ -170,10 +184,10 @@
}
@Override
- public void showSessionFailed() {
+ public void showSessionFailed(@NonNull Bundle args) {
Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
VoiceInteractionService::onShowSessionFailed,
- VoiceInteractionService.this));
+ VoiceInteractionService.this, args));
}
};
@@ -205,9 +219,10 @@
* bind the session service.
*
* @param args The arguments that were supplied to {@link #showSession(Bundle, int)}.
+ * It always includes {@link #KEY_SHOW_SESSION_ID}.
* @param flags The show flags originally provided to {@link #showSession(Bundle, int)}.
* @see #showSession(Bundle, int)
- * @see #onShowSessionFailed()
+ * @see #onShowSessionFailed(Bundle)
* @see VoiceInteractionSession#onShow(Bundle, int)
* @see VoiceInteractionSession#show(Bundle, int)
*/
@@ -217,12 +232,14 @@
/**
* Called when the show session failed. E.g. When the system bound the session service failed.
*
+ * @param args Additional info about the show session attempt that failed. For now, includes
+ * {@link #KEY_SHOW_SESSION_ID}.
* @see #showSession(Bundle, int)
* @see #onPrepareToShowSession(Bundle, int)
* @see VoiceInteractionSession#onShow(Bundle, int)
* @see VoiceInteractionSession#show(Bundle, int)
*/
- public void onShowSessionFailed() {
+ public void onShowSessionFailed(@NonNull Bundle args) {
}
/**
@@ -719,8 +736,8 @@
private void onHotwordDetectorDestroyed(@NonNull HotwordDetector detector) {
synchronized (mLock) {
- if (mActiveVisualQueryDetector!= null &&
- detector == mActiveVisualQueryDetector.getInitializationDelegate()) {
+ if (mActiveVisualQueryDetector != null
+ && detector == mActiveVisualQueryDetector.getInitializationDelegate()) {
mActiveVisualQueryDetector = null;
}
mActiveDetectors.remove(detector);
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 2c70229..d55fede 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1763,7 +1763,8 @@
* @param args The arguments that were supplied to
* {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
* Some example keys include : "invocation_type", "invocation_phone_state",
- * "invocation_time_ms", Intent.EXTRA_TIME ("android.intent.extra.TIME") indicating timing
+ * {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, "invocation_time_ms",
+ * Intent.EXTRA_TIME ("android.intent.extra.TIME") indicating timing
* in milliseconds of the KeyEvent that triggered Assistant and
* Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)
* referring to the device that sent the request.
diff --git a/core/java/com/android/internal/config/appcloning/AppCloningDeviceConfigHelper.java b/core/java/com/android/internal/config/appcloning/AppCloningDeviceConfigHelper.java
new file mode 100644
index 0000000..ddd3d2c
--- /dev/null
+++ b/core/java/com/android/internal/config/appcloning/AppCloningDeviceConfigHelper.java
@@ -0,0 +1,107 @@
+/*
+ * 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.internal.config.appcloning;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Helper class that holds the flags related to the app_cloning namespace in {@link DeviceConfig}.
+ *
+ * @hide
+ */
+public class AppCloningDeviceConfigHelper {
+
+ @GuardedBy("sLock")
+ private static AppCloningDeviceConfigHelper sInstance;
+
+ private static final Object sLock = new Object();
+
+ private DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangeListener;
+
+ /**
+ * This flag is defined inside {@link DeviceConfig#NAMESPACE_APP_CLONING}. Please check
+ * {@link #mEnableAppCloningBuildingBlocks} for details.
+ */
+ public static final String ENABLE_APP_CLONING_BUILDING_BLOCKS =
+ "enable_app_cloning_building_blocks";
+
+ /**
+ * Checks whether the support for app-cloning building blocks (like contacts
+ * sharing/intent redirection), which are available starting from the U release, is turned on.
+ * The default value is true to ensure the features are always enabled going forward.
+ *
+ * TODO:(b/253449368) Add information about the app-cloning config and mention that the devices
+ * that do not support app-cloning should use the app-cloning config to disable all app-cloning
+ * features.
+ */
+ private volatile boolean mEnableAppCloningBuildingBlocks = true;
+
+ private AppCloningDeviceConfigHelper() {}
+
+ /**
+ * @hide
+ */
+ public static AppCloningDeviceConfigHelper getInstance(Context context) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new AppCloningDeviceConfigHelper();
+ sInstance.init(context);
+ }
+ return sInstance;
+ }
+ }
+
+ private void init(Context context) {
+ initializeDeviceConfigChangeListener();
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_CLONING,
+ context.getMainExecutor(),
+ mDeviceConfigChangeListener);
+ }
+
+ private void initializeDeviceConfigChangeListener() {
+ mDeviceConfigChangeListener = properties -> {
+ if (!DeviceConfig.NAMESPACE_APP_CLONING.equals(properties.getNamespace())) {
+ return;
+ }
+ for (String name : properties.getKeyset()) {
+ if (name == null) {
+ return;
+ }
+ if (ENABLE_APP_CLONING_BUILDING_BLOCKS.equals(name)) {
+ updateEnableAppCloningBuildingBlocks();
+ }
+ }
+ };
+ }
+
+ private void updateEnableAppCloningBuildingBlocks() {
+ mEnableAppCloningBuildingBlocks = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_APP_CLONING, ENABLE_APP_CLONING_BUILDING_BLOCKS, true);
+ }
+
+ /**
+ * Fetch the feature flag to check whether the support for the app-cloning building blocks
+ * (like contacts sharing/intent redirection) is enabled on the device.
+ * @hide
+ */
+ public boolean getEnableAppCloningBuildingBlocks() {
+ return mEnableAppCloningBuildingBlocks;
+ }
+}
diff --git a/core/java/com/android/internal/config/appcloning/OWNERS b/core/java/com/android/internal/config/appcloning/OWNERS
new file mode 100644
index 0000000..0645a8c5
--- /dev/null
+++ b/core/java/com/android/internal/config/appcloning/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1207885
+jigarthakkar@google.com
+saumyap@google.com
\ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 07d0158..c5d932e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -79,6 +79,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.extensions.core.util.function.Function;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
@@ -563,10 +564,10 @@
SplitAttributes.SplitType.RatioSplitType.splitEqually()
)
).build();
+ final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
+ params -> splitAttributes;
- mController.setSplitAttributesCalculator(params -> {
- return splitAttributes;
- });
+ mController.setSplitAttributesCalculator(calculator);
assertEquals(splitAttributes, mPresenter.computeSplitAttributes(taskProperties,
splitPairRule, null /* minDimensionsPair */));
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 5de5365..cddbf469 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 8121b20..e85b3c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -232,10 +232,13 @@
if (mBubble.isAppBubble()) {
PendingIntent pi = PendingIntent.getActivity(mContext, 0,
- mBubble.getAppBubbleIntent(),
- PendingIntent.FLAG_MUTABLE,
+ mBubble.getAppBubbleIntent()
+ .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
+ PendingIntent.FLAG_IMMUTABLE,
null);
- mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+ mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
+ launchBounds);
} else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
options.setApplyActivityFlagsForBubbles(true);
mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index fcbf9e0..fa3a6ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -78,6 +78,7 @@
private final Rect mResizingBounds = new Rect();
private final Rect mTempRect = new Rect();
private ValueAnimator mFadeAnimator;
+ private ValueAnimator mScreenshotAnimator;
private int mIconSize;
private int mOffsetX;
@@ -135,8 +136,17 @@
/** Releases the surfaces for split decor. */
public void release(SurfaceControl.Transaction t) {
- if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
- mFadeAnimator.cancel();
+ if (mFadeAnimator != null) {
+ if (mFadeAnimator.isRunning()) {
+ mFadeAnimator.cancel();
+ }
+ mFadeAnimator = null;
+ }
+ if (mScreenshotAnimator != null) {
+ if (mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+ mScreenshotAnimator = null;
}
if (mViewHost != null) {
mViewHost.release();
@@ -238,16 +248,20 @@
/** Stops showing resizing hint. */
public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
if (mScreenshot != null) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+
t.setPosition(mScreenshot, mOffsetX, mOffsetY);
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
- final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
- va.addUpdateListener(valueAnimator -> {
+ mScreenshotAnimator = ValueAnimator.ofFloat(1, 0);
+ mScreenshotAnimator.addUpdateListener(valueAnimator -> {
final float progress = (float) valueAnimator.getAnimatedValue();
animT.setAlpha(mScreenshot, progress);
animT.apply();
});
- va.addListener(new AnimatorListenerAdapter() {
+ mScreenshotAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mRunningAnimationCount++;
@@ -266,7 +280,7 @@
}
}
});
- va.start();
+ mScreenshotAnimator.start();
}
if (mResizingIconView == null) {
@@ -292,9 +306,6 @@
});
return;
}
-
- // If fade-in animation is running, cancel it and re-run fade-out one.
- mFadeAnimator.cancel();
}
if (mShown) {
fadeOutDecor(animFinishedCallback);
@@ -332,6 +343,11 @@
* directly. */
public void fadeOutDecor(Runnable finishedCallback) {
if (mShown) {
+ // If previous animation is running, just cancel it.
+ if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
+ mFadeAnimator.cancel();
+ }
+
startFadeAnimation(false /* show */, true, finishedCallback);
mShown = false;
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
index b189163..8d4a384 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
@@ -125,7 +125,9 @@
cancelScheduledPlacement();
applyPlacement(placement, shouldStash, animationDuration);
} else {
- applyPlacementBounds(mCurrentPlacementBounds, animationDuration);
+ if (mCurrentPlacementBounds != null) {
+ applyPlacementBounds(mCurrentPlacementBounds, animationDuration);
+ }
schedulePinnedStackPlacement(placement, animationDuration);
}
}
@@ -188,7 +190,7 @@
applyPlacementBounds(bounds, animationDuration);
}
- void onPipDismissed() {
+ void reset() {
mCurrentPlacementBounds = null;
mPipTargetBounds = null;
cancelScheduledPlacement();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index fd4fcff..4426423 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -450,18 +450,6 @@
mPipMediaController.registerSessionListenerForCurrentUser();
}
- private void checkIfPinnedTaskAppeared() {
- final TaskInfo pinnedTask = getPinnedTaskInfo();
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: checkIfPinnedTaskAppeared(), task=%s", TAG, pinnedTask);
- if (pinnedTask == null || pinnedTask.topActivity == null) return;
- mPinnedTaskId = pinnedTask.taskId;
-
- mPipMediaController.onActivityPinned();
- mActionBroadcastReceiver.register();
- mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
- }
-
private void checkIfPinnedTaskIsGone() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onTaskStackChanged()", TAG);
@@ -482,7 +470,7 @@
mTvPipMenuController.closeMenu();
mTvPipBoundsState.resetTvPipState();
- mTvPipBoundsController.onPipDismissed();
+ mTvPipBoundsController.reset();
setState(STATE_NO_PIP);
mPinnedTaskId = NONEXISTENT_TASK_ID;
}
@@ -537,7 +525,16 @@
taskStackListener.addListener(new TaskStackListenerCallback() {
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
- checkIfPinnedTaskAppeared();
+ final TaskInfo pinnedTask = getPinnedTaskInfo();
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onActivityPinned(), task=%s", TAG, pinnedTask);
+ if (pinnedTask == null || pinnedTask.topActivity == null) return;
+ mPinnedTaskId = pinnedTask.taskId;
+
+ mPipMediaController.onActivityPinned();
+ mActionBroadcastReceiver.register();
+ mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
+ mTvPipBoundsController.reset();
mAppOpsListener.onActivityPinned(packageName);
}
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index 98819a3..0198419 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -28,11 +28,11 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
/**
* @hide
@@ -140,7 +140,7 @@
*/
public static int getLegacyStreamTypeForStrategyWithAudioAttributes(
@NonNull AudioAttributes audioAttributes) {
- Preconditions.checkNotNull(audioAttributes, "AudioAttributes must not be null");
+ Objects.requireNonNull(audioAttributes, "AudioAttributes must not be null");
for (final AudioProductStrategy productStrategy :
AudioProductStrategy.getAudioProductStrategies()) {
if (productStrategy.supportsAudioAttributes(audioAttributes)) {
@@ -160,6 +160,30 @@
return AudioSystem.STREAM_MUSIC;
}
+ /**
+ * @hide
+ * @param attributes the {@link AudioAttributes} to identify VolumeGroupId with
+ * @param fallbackOnDefault if set, allows to fallback on the default group (e.g. the group
+ * associated to {@link AudioManager#STREAM_MUSIC}).
+ * @return volume group id associated with the given {@link AudioAttributes} if found,
+ * default volume group id if fallbackOnDefault is set
+ * <p>By convention, the product strategy with default attributes will be associated to the
+ * default volume group (e.g. associated to {@link AudioManager#STREAM_MUSIC})
+ * or {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} if not found.
+ */
+ public static int getVolumeGroupIdForAudioAttributes(
+ @NonNull AudioAttributes attributes, boolean fallbackOnDefault) {
+ Objects.requireNonNull(attributes, "attributes must not be null");
+ int volumeGroupId = getVolumeGroupIdForAudioAttributesInt(attributes);
+ if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
+ return volumeGroupId;
+ }
+ if (fallbackOnDefault) {
+ return getVolumeGroupIdForAudioAttributesInt(getDefaultAttributes());
+ }
+ return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
+ }
+
private static List<AudioProductStrategy> initializeAudioProductStrategies() {
ArrayList<AudioProductStrategy> apsList = new ArrayList<AudioProductStrategy>();
int status = native_list_audio_product_strategies(apsList);
@@ -190,8 +214,8 @@
*/
private AudioProductStrategy(@NonNull String name, int id,
@NonNull AudioAttributesGroup[] aag) {
- Preconditions.checkNotNull(name, "name must not be null");
- Preconditions.checkNotNull(aag, "AudioAttributesGroups must not be null");
+ Objects.requireNonNull(name, "name must not be null");
+ Objects.requireNonNull(aag, "AudioAttributesGroups must not be null");
mName = name;
mId = id;
mAudioAttributesGroups = aag;
@@ -241,7 +265,7 @@
*/
@TestApi
public int getLegacyStreamTypeForAudioAttributes(@NonNull AudioAttributes aa) {
- Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
+ Objects.requireNonNull(aa, "AudioAttributes must not be null");
for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
if (aag.supportsAttributes(aa)) {
return aag.getStreamType();
@@ -258,7 +282,7 @@
*/
@SystemApi
public boolean supportsAudioAttributes(@NonNull AudioAttributes aa) {
- Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
+ Objects.requireNonNull(aa, "AudioAttributes must not be null");
for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
if (aag.supportsAttributes(aa)) {
return true;
@@ -291,7 +315,7 @@
*/
@TestApi
public int getVolumeGroupIdForAudioAttributes(@NonNull AudioAttributes aa) {
- Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
+ Objects.requireNonNull(aa, "AudioAttributes must not be null");
for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
if (aag.supportsAttributes(aa)) {
return aag.getVolumeGroupId();
@@ -300,6 +324,17 @@
return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
}
+ private static int getVolumeGroupIdForAudioAttributesInt(@NonNull AudioAttributes attributes) {
+ Objects.requireNonNull(attributes, "attributes must not be null");
+ for (AudioProductStrategy productStrategy : getAudioProductStrategies()) {
+ int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes);
+ if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
+ return volumeGroupId;
+ }
+ }
+ return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -374,8 +409,8 @@
*/
private static boolean attributesMatches(@NonNull AudioAttributes refAttr,
@NonNull AudioAttributes attr) {
- Preconditions.checkNotNull(refAttr, "refAttr must not be null");
- Preconditions.checkNotNull(attr, "attr must not be null");
+ Objects.requireNonNull(refAttr, "reference AudioAttributes must not be null");
+ Objects.requireNonNull(attr, "requester's AudioAttributes must not be null");
String refFormattedTags = TextUtils.join(";", refAttr.getTags());
String cliFormattedTags = TextUtils.join(";", attr.getTags());
if (refAttr.equals(DEFAULT_ATTRIBUTES)) {
diff --git a/media/java/android/media/projection/TEST_MAPPING b/media/java/android/media/projection/TEST_MAPPING
new file mode 100644
index 0000000..a792498
--- /dev/null
+++ b/media/java/android/media/projection/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+ "presubmit": [
+ {
+ "name": "MediaProjectionTests",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/media/tests/projection/TEST_MAPPING b/media/tests/projection/TEST_MAPPING
deleted file mode 100644
index ddb68af..0000000
--- a/media/tests/projection/TEST_MAPPING
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "presubmit": [
- {
- "name": "FrameworksServicesTests",
- "options": [
- {"include-filter": "android.media.projection.mediaprojectiontests"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"},
- {"exclude-annotation": "org.junit.Ignore"}
- ]
- }
- ]
-}
diff --git a/packages/SettingsLib/HelpUtils/Android.bp b/packages/SettingsLib/HelpUtils/Android.bp
index aea51b1..3ec4366a 100644
--- a/packages/SettingsLib/HelpUtils/Android.bp
+++ b/packages/SettingsLib/HelpUtils/Android.bp
@@ -22,5 +22,6 @@
apex_available: [
"//apex_available:platform",
"com.android.permission",
+ "com.android.healthconnect",
],
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 90d3488..115cf792 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -343,6 +343,7 @@
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
+ <protected-broadcast android:name="com.android.systemui.STARTED" />
<application
android:name=".SystemUIApplication"
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index 259f0ed..4e96dda 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -41,6 +41,7 @@
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCompositionContext
@@ -73,6 +74,7 @@
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
+import com.android.compose.runtime.movableContentOf
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.LaunchAnimator
import kotlin.math.max
@@ -170,25 +172,25 @@
val contentColor = controller.contentColor
val shape = controller.shape
- // TODO(b/230830644): Use movableContentOf to preserve the content state instead once the
- // Compose libraries have been updated and include aosp/2163631.
val wrappedContent =
- @Composable { controller: ExpandableController ->
- CompositionLocalProvider(
- LocalContentColor provides contentColor,
- ) {
- // We make sure that the content itself (wrapped by the background) is at least
- // 40.dp, which is the same as the M3 buttons. This applies even if onClick is
- // null, to make it easier to write expandables that are sometimes clickable and
- // sometimes not. There shouldn't be any Expandable smaller than 40dp because if
- // the expandable is not clickable directly, then something in its content should
- // be (and with a size >= 40dp).
- val minSize = 40.dp
- Box(
- Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
- contentAlignment = Alignment.Center,
+ remember(content) {
+ movableContentOf { expandable: Expandable ->
+ CompositionLocalProvider(
+ LocalContentColor provides contentColor,
) {
- content(controller.expandable)
+ // We make sure that the content itself (wrapped by the background) is at least
+ // 40.dp, which is the same as the M3 buttons. This applies even if onClick is
+ // null, to make it easier to write expandables that are sometimes clickable and
+ // sometimes not. There shouldn't be any Expandable smaller than 40dp because if
+ // the expandable is not clickable directly, then something in its content
+ // should be (and with a size >= 40dp).
+ val minSize = 40.dp
+ Box(
+ Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
+ contentAlignment = Alignment.Center,
+ ) {
+ content(expandable)
+ }
}
}
}
@@ -270,7 +272,7 @@
.onGloballyPositioned {
controller.boundsInComposeViewRoot.value = it.boundsInRoot()
}
- ) { wrappedContent(controller) }
+ ) { wrappedContent(controller.expandable) }
}
else -> {
val clickModifier =
@@ -301,7 +303,7 @@
controller.boundsInComposeViewRoot.value = it.boundsInRoot()
},
) {
- wrappedContent(controller)
+ wrappedContent(controller.expandable)
}
}
}
@@ -315,7 +317,7 @@
animatorState: State<LaunchAnimator.State?>,
overlay: ViewGroupOverlay,
controller: ExpandableControllerImpl,
- content: @Composable (ExpandableController) -> Unit,
+ content: @Composable (Expandable) -> Unit,
composeViewRoot: View,
onOverlayComposeViewChanged: (View?) -> Unit,
density: Density,
@@ -370,7 +372,7 @@
// We center the content in the expanding container.
contentAlignment = Alignment.Center,
) {
- Box(contentModifier) { content(controller) }
+ Box(contentModifier) { content(controller.expandable) }
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
index 24ae42a..fe607e1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -19,7 +19,7 @@
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldBackground
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
@@ -56,6 +56,6 @@
abstract fun mainHandler(@Main handler: Handler): Handler
@Binds
- @UnfoldBackground
+ @UnfoldSingleThreadBg
abstract fun backgroundExecutor(@UiBackground executor: Executor): Executor
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f60f9a1..a5d9fee 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -411,6 +411,17 @@
val WM_DESKTOP_WINDOWING_2 =
sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false)
+ // TODO(b/254513207): Tracking Bug to delete
+ @Keep
+ @JvmField
+ val WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES =
+ unreleasedFlag(
+ 1113,
+ name = "screen_record_enterprise_policies",
+ namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ teamfood = false
+ )
+
// 1200 - predictive back
@Keep
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index ceb4845..a692ad7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -18,6 +18,7 @@
import android.app.ActivityOptions
import android.content.Intent
import android.content.res.Configuration
+import android.content.res.Resources
import android.media.projection.IMediaProjection
import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
import android.os.Binder
@@ -27,6 +28,7 @@
import android.os.UserHandle
import android.view.ViewGroup
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider
import com.android.internal.app.ChooserActivity
import com.android.internal.app.ResolverListController
import com.android.internal.app.chooser.NotSelectableTargetInfo
@@ -59,16 +61,12 @@
private lateinit var configurationController: ConfigurationController
private lateinit var controller: MediaProjectionAppSelectorController
private lateinit var recentsViewController: MediaProjectionRecentsViewController
+ private lateinit var component: MediaProjectionAppSelectorComponent
override fun getLayoutResource() = R.layout.media_projection_app_selector
public override fun onCreate(bundle: Bundle?) {
- val component =
- componentFactory.create(
- activity = this,
- view = this,
- resultHandler = this
- )
+ component = componentFactory.create(activity = this, view = this, resultHandler = this)
// Create a separate configuration controller for this activity as the configuration
// might be different from the global one
@@ -76,11 +74,12 @@
controller = component.controller
recentsViewController = component.recentsViewController
- val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
- intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
+ intent.configureChooserIntent(
+ resources,
+ component.hostUserHandle,
+ component.personalProfileUserHandle
+ )
- val title = getString(R.string.media_projection_permission_app_selector_title)
- intent.putExtra(Intent.EXTRA_TITLE, title)
super.onCreate(bundle)
controller.init()
}
@@ -183,6 +182,13 @@
override fun shouldShowContentPreview() = true
+ override fun shouldShowContentPreviewWhenEmpty(): Boolean = true
+
+ override fun createMyUserIdProvider(): MyUserIdProvider =
+ object : MyUserIdProvider() {
+ override fun getMyUserId(): Int = component.hostUserHandle.identifier
+ }
+
override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
recentsViewController.createView(parent)
@@ -193,6 +199,34 @@
* instance through activity result.
*/
const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
+
+ /** UID of the app that originally launched the media projection flow (host app user) */
+ const val EXTRA_HOST_APP_USER_HANDLE = "launched_from_user_handle"
const val KEY_CAPTURE_TARGET = "capture_region"
+
+ /** Set up intent for the [ChooserActivity] */
+ private fun Intent.configureChooserIntent(
+ resources: Resources,
+ hostUserHandle: UserHandle,
+ personalProfileUserHandle: UserHandle
+ ) {
+ // Specify the query intent to show icons for all apps on the chooser screen
+ val queryIntent =
+ Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
+ putExtra(Intent.EXTRA_INTENT, queryIntent)
+
+ // Update the title of the chooser
+ val title = resources.getString(R.string.media_projection_permission_app_selector_title)
+ putExtra(Intent.EXTRA_TITLE, title)
+
+ // Select host app's profile tab by default
+ val selectedProfile =
+ if (hostUserHandle == personalProfileUserHandle) {
+ PROFILE_PERSONAL
+ } else {
+ PROFILE_WORK
+ }
+ putExtra(EXTRA_SELECTED_PROFILE, selectedProfile)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index bfa67a8..d830fc4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -22,6 +22,7 @@
import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
@@ -35,6 +36,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.text.BidiFormatter;
import android.text.SpannableString;
import android.text.TextPaint;
@@ -208,8 +210,14 @@
final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
projection.asBinder());
+ intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
+ UserHandle.getUserHandleForUid(getLaunchedFromUid()));
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
- startActivity(intent);
+
+ // Start activity from the current foreground user to avoid creating a separate
+ // SystemUI process without access to recent tasks because it won't have
+ // WM Shell running inside.
+ startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
}
} catch (RemoteException e) {
Log.e(TAG, "Error granting projection permission", e);
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 6c41caa..1d86343 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -19,9 +19,11 @@
import android.app.Activity
import android.content.ComponentName
import android.content.Context
+import android.os.UserHandle
import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import com.android.systemui.media.MediaProjectionAppSelectorActivity.Companion.EXTRA_HOST_APP_USER_HANDLE
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
@@ -30,6 +32,8 @@
import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.Binds
@@ -39,6 +43,7 @@
import dagger.Subcomponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import java.lang.IllegalArgumentException
import javax.inject.Qualifier
import javax.inject.Scope
import kotlinx.coroutines.CoroutineScope
@@ -46,6 +51,12 @@
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile
+
@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
@Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
@@ -83,7 +94,7 @@
@MediaProjectionAppSelector
@MediaProjectionAppSelectorScope
fun provideAppSelectorComponentName(context: Context): ComponentName =
- ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
+ ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
@Provides
@MediaProjectionAppSelector
@@ -93,9 +104,32 @@
): ConfigurationController = ConfigurationControllerImpl(activity)
@Provides
- fun bindIconFactory(
- context: Context
- ): IconFactory = IconFactory.obtain(context)
+ @PersonalProfile
+ @MediaProjectionAppSelectorScope
+ fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
+ // Current foreground user is the 'personal' profile
+ return UserHandle.of(activityManagerWrapper.currentUserId)
+ }
+
+ @Provides
+ @WorkProfile
+ @MediaProjectionAppSelectorScope
+ fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
+ userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
+
+ @Provides
+ @HostUserHandle
+ @MediaProjectionAppSelectorScope
+ fun hostUserHandle(activity: MediaProjectionAppSelectorActivity): UserHandle {
+ val extras =
+ activity.intent.extras
+ ?: error("MediaProjectionAppSelectorActivity should be launched with extras")
+ return extras.getParcelable(EXTRA_HOST_APP_USER_HANDLE)
+ ?: error("MediaProjectionAppSelectorActivity should be provided with " +
+ "$EXTRA_HOST_APP_USER_HANDLE extra")
+ }
+
+ @Provides fun bindIconFactory(context: Context): IconFactory = IconFactory.obtain(context)
@Provides
@MediaProjectionAppSelector
@@ -124,6 +158,8 @@
val controller: MediaProjectionAppSelectorController
val recentsViewController: MediaProjectionRecentsViewController
+ @get:HostUserHandle val hostUserHandle: UserHandle
+ @get:PersonalProfile val personalProfileUserHandle: UserHandle
@MediaProjectionAppSelector val configurationController: ConfigurationController
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index d744a40b..52c7ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -17,24 +17,36 @@
package com.android.systemui.mediaprojection.appselector
import android.content.ComponentName
+import android.os.UserHandle
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
-import javax.inject.Inject
@MediaProjectionAppSelectorScope
-class MediaProjectionAppSelectorController @Inject constructor(
+class MediaProjectionAppSelectorController
+@Inject
+constructor(
private val recentTaskListProvider: RecentTaskListProvider,
private val view: MediaProjectionAppSelectorView,
+ private val flags: FeatureFlags,
+ @HostUserHandle private val hostUserHandle: UserHandle,
@MediaProjectionAppSelector private val scope: CoroutineScope,
@MediaProjectionAppSelector private val appSelectorComponentName: ComponentName
) {
fun init() {
scope.launch {
- val tasks = recentTaskListProvider.loadRecentTasks().sortTasks()
+ val recentTasks = recentTaskListProvider.loadRecentTasks()
+
+ val tasks = recentTasks
+ .filterDevicePolicyRestrictedTasks()
+ .sortedTasks()
+
view.bind(tasks)
}
}
@@ -43,9 +55,20 @@
scope.cancel()
}
- private fun List<RecentTask>.sortTasks(): List<RecentTask> =
- sortedBy {
- // Show normal tasks first and only then tasks with opened app selector
- it.topActivityComponent == appSelectorComponentName
+ /**
+ * Removes all recent tasks that are different from the profile of the host app to avoid any
+ * cross-profile sharing
+ */
+ private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> =
+ if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
+ // TODO(b/263950746): filter tasks based on the enterprise policies
+ this
+ } else {
+ filter { UserHandle.of(it.userId) == hostUserHandle }
}
+
+ private fun List<RecentTask>.sortedTasks(): List<RecentTask> = sortedBy {
+ // Show normal tasks first and only then tasks with opened app selector
+ it.topActivityComponent == appSelectorComponentName
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index cd994b8..41e2286 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -17,11 +17,12 @@
package com.android.systemui.mediaprojection.appselector.data
import android.annotation.ColorInt
+import android.annotation.UserIdInt
import android.content.ComponentName
data class RecentTask(
val taskId: Int,
- val userId: Int,
+ @UserIdInt val userId: Int,
val topActivityComponent: ComponentName?,
val baseIntentComponent: ComponentName?,
@ColorInt val colorBackground: Int?
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index 44b18ec..68e3dcd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -23,6 +23,7 @@
import android.os.Handler
import android.os.Looper
import android.os.ResultReceiver
+import android.os.UserHandle
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
@@ -77,6 +78,14 @@
MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
CaptureTargetResultReceiver()
)
+
+ // Send SystemUI's user handle as the host app user handle because SystemUI
+ // is the 'host app' (the app that receives screen capture data)
+ intent.putExtra(
+ MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
+ UserHandle.of(UserHandle.myUserId())
+ )
+
val animationController = dialogLaunchAnimator.createActivityLaunchController(v!!)
if (animationController == null) {
dismiss()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 964d0b2..fd31e49 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -715,7 +715,7 @@
updatePanelExpansionAndVisibility();
};
private final Runnable mMaybeHideExpandedRunnable = () -> {
- if (getExpansionFraction() == 0.0f) {
+ if (getExpandedFraction() == 0.0f) {
postToView(mHideExpandedRunnable);
}
};
@@ -5451,10 +5451,6 @@
InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
}
- private float getExpansionFraction() {
- return mExpandedFraction;
- }
-
private ShadeExpansionStateManager getShadeExpansionStateManager() {
return mShadeExpansionStateManager;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 19d2d33..1042ea7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -1,12 +1,16 @@
package com.android.systemui.mediaprojection.appselector
import android.content.ComponentName
+import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.junit.Test
@@ -21,11 +25,17 @@
private val scope = CoroutineScope(Dispatchers.Unconfined)
private val appSelectorComponentName = ComponentName("com.test", "AppSelector")
+ private val hostUserHandle = UserHandle.of(123)
+ private val otherUserHandle = UserHandle.of(456)
+
private val view: MediaProjectionAppSelectorView = mock()
+ private val featureFlags: FeatureFlags = mock()
private val controller = MediaProjectionAppSelectorController(
taskListProvider,
view,
+ featureFlags,
+ hostUserHandle,
scope,
appSelectorComponentName
)
@@ -98,15 +108,72 @@
)
}
+ @Test
+ fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsOnlyTasksWithHostProfile() {
+ givenEnterprisePoliciesFeatureFlag(enabled = false)
+
+ val tasks = listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ taskListProvider.tasks = tasks
+
+ controller.init()
+
+ verify(view).bind(
+ listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ )
+ }
+
+ @Test
+ fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesEnabled_bindsAllTasks() {
+ givenEnterprisePoliciesFeatureFlag(enabled = true)
+
+ val tasks = listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ taskListProvider.tasks = tasks
+
+ controller.init()
+
+ // TODO(b/233348916) should filter depending on the policies
+ verify(view).bind(
+ listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ )
+ }
+
+ private fun givenEnterprisePoliciesFeatureFlag(enabled: Boolean) {
+ whenever(featureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+ .thenReturn(enabled)
+ }
+
private fun createRecentTask(
taskId: Int,
- topActivityComponent: ComponentName? = null
+ topActivityComponent: ComponentName? = null,
+ userId: Int = hostUserHandle.identifier
): RecentTask {
return RecentTask(
taskId = taskId,
topActivityComponent = topActivityComponent,
baseIntentComponent = ComponentName("com", "Test"),
- userId = 0,
+ userId = userId,
colorBackground = 0
)
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index 5a868a4..cfb959e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -22,8 +22,8 @@
import android.os.Handler
import android.view.IWindowManager
import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldBackground
import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
@@ -58,7 +58,7 @@
@BindsInstance sensorManager: SensorManager,
@BindsInstance @UnfoldMain handler: Handler,
@BindsInstance @UnfoldMain executor: Executor,
- @BindsInstance @UnfoldBackground backgroundExecutor: Executor,
+ @BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor,
@BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
@BindsInstance windowManager: IWindowManager,
@BindsInstance contentResolver: ContentResolver = context.contentResolver
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index 3fa5469..31616fa 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -16,9 +16,7 @@
package com.android.systemui.unfold
-import android.hardware.SensorManager
import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldBackground
import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
import com.android.systemui.unfold.updates.DeviceFoldStateProvider
@@ -34,55 +32,18 @@
import dagger.Module
import dagger.Provides
import java.util.Optional
-import java.util.concurrent.Executor
+import javax.inject.Provider
import javax.inject.Singleton
-@Module
+@Module(includes = [UnfoldSharedInternalModule::class])
class UnfoldSharedModule {
@Provides
@Singleton
- fun unfoldTransitionProgressProvider(
- config: UnfoldTransitionConfig,
- scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
- tracingListener: ATraceLoggerTransitionProgressListener,
- foldStateProvider: FoldStateProvider
- ): Optional<UnfoldTransitionProgressProvider> =
- if (!config.isEnabled) {
- Optional.empty()
- } else {
- val baseProgressProvider =
- if (config.isHingeAngleEnabled) {
- PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
- } else {
- FixedTimingTransitionProgressProvider(foldStateProvider)
- }
- Optional.of(
- scaleAwareProviderFactory.wrap(baseProgressProvider).apply {
- // Always present callback that logs animation beginning and end.
- addCallback(tracingListener)
- }
- )
- }
-
- @Provides
- @Singleton
fun provideFoldStateProvider(
deviceFoldStateProvider: DeviceFoldStateProvider
): FoldStateProvider = deviceFoldStateProvider
@Provides
- fun hingeAngleProvider(
- config: UnfoldTransitionConfig,
- sensorManager: SensorManager,
- @UnfoldBackground executor: Executor
- ): HingeAngleProvider =
- if (config.isHingeAngleEnabled) {
- HingeSensorAngleProvider(sensorManager, executor)
- } else {
- EmptyHingeAngleProvider
- }
-
- @Provides
@Singleton
fun unfoldKeyguardVisibilityProvider(
impl: UnfoldKeyguardVisibilityManagerImpl
@@ -94,3 +55,51 @@
impl: UnfoldKeyguardVisibilityManagerImpl
): UnfoldKeyguardVisibilityManager = impl
}
+
+/**
+ * Needed as methods inside must be public, but their parameters can be internal (and, a public
+ * method can't have internal parameters). Making the module internal and included in a public one
+ * fixes the issue.
+ */
+@Module
+internal class UnfoldSharedInternalModule {
+ @Provides
+ @Singleton
+ fun unfoldTransitionProgressProvider(
+ config: UnfoldTransitionConfig,
+ scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
+ tracingListener: ATraceLoggerTransitionProgressListener,
+ physicsBasedUnfoldTransitionProgressProvider:
+ Provider<PhysicsBasedUnfoldTransitionProgressProvider>,
+ fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>,
+ ): Optional<UnfoldTransitionProgressProvider> {
+ if (!config.isEnabled) {
+ return Optional.empty()
+ }
+ val baseProgressProvider =
+ if (config.isHingeAngleEnabled) {
+ physicsBasedUnfoldTransitionProgressProvider.get()
+ } else {
+ fixedTimingTransitionProgressProvider.get()
+ }
+
+ return Optional.of(
+ scaleAwareProviderFactory.wrap(baseProgressProvider).apply {
+ // Always present callback that logs animation beginning and end.
+ addCallback(tracingListener)
+ }
+ )
+ }
+
+ @Provides
+ fun hingeAngleProvider(
+ config: UnfoldTransitionConfig,
+ hingeAngleSensorProvider: Provider<HingeSensorAngleProvider>
+ ): HingeAngleProvider {
+ return if (config.isHingeAngleEnabled) {
+ hingeAngleSensorProvider.get()
+ } else {
+ EmptyHingeAngleProvider
+ }
+ }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index a1ed178..aa93c629 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -37,29 +37,29 @@
* This should **never** be called from sysui, as the object is already provided in that process.
*/
fun createUnfoldSharedComponent(
- context: Context,
- config: UnfoldTransitionConfig,
- screenStatusProvider: ScreenStatusProvider,
- foldProvider: FoldProvider,
- activityTypeProvider: CurrentActivityTypeProvider,
- sensorManager: SensorManager,
- mainHandler: Handler,
- mainExecutor: Executor,
- backgroundExecutor: Executor,
- tracingTagPrefix: String,
- windowManager: IWindowManager,
+ context: Context,
+ config: UnfoldTransitionConfig,
+ screenStatusProvider: ScreenStatusProvider,
+ foldProvider: FoldProvider,
+ activityTypeProvider: CurrentActivityTypeProvider,
+ sensorManager: SensorManager,
+ mainHandler: Handler,
+ mainExecutor: Executor,
+ singleThreadBgExecutor: Executor,
+ tracingTagPrefix: String,
+ windowManager: IWindowManager,
): UnfoldSharedComponent =
- DaggerUnfoldSharedComponent.factory()
- .create(
- context,
- config,
- screenStatusProvider,
- foldProvider,
- activityTypeProvider,
- sensorManager,
- mainHandler,
- mainExecutor,
- backgroundExecutor,
- tracingTagPrefix,
- windowManager,
- )
+ DaggerUnfoldSharedComponent.factory()
+ .create(
+ context,
+ config,
+ screenStatusProvider,
+ foldProvider,
+ activityTypeProvider,
+ sensorManager,
+ mainHandler,
+ mainExecutor,
+ singleThreadBgExecutor,
+ tracingTagPrefix,
+ windowManager,
+ )
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldSingleThreadBg.kt
similarity index 89%
rename from packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldSingleThreadBg.kt
index 6074795..dcac531 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldSingleThreadBg.kt
@@ -18,8 +18,7 @@
/**
* Alternative to [UiBackground] qualifier annotation in unfold module.
+ *
* It is needed as we can't depend on SystemUI code in this module.
*/
-@Qualifier
-@Retention(AnnotationRetention.RUNTIME)
-annotation class UnfoldBackground
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class UnfoldSingleThreadBg
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
index fa59cb4..4622464 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
@@ -24,11 +24,13 @@
import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
+import javax.inject.Inject
/** Emits animation progress with fixed timing after unfolding */
-internal class FixedTimingTransitionProgressProvider(
- private val foldStateProvider: FoldStateProvider
-) : UnfoldTransitionProgressProvider, FoldStateProvider.FoldUpdatesListener {
+internal class FixedTimingTransitionProgressProvider
+@Inject
+constructor(private val foldStateProvider: FoldStateProvider) :
+ UnfoldTransitionProgressProvider, FoldStateProvider.FoldUpdatesListener {
private val animatorListener = AnimatorListener()
private val animator =
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 074b1e1..6ffbe5a 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -33,9 +33,10 @@
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
import com.android.systemui.unfold.updates.name
+import javax.inject.Inject
/** Maps fold updates to unfold transition progress using DynamicAnimation. */
-class PhysicsBasedUnfoldTransitionProgressProvider(
+class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
private val foldStateProvider: FoldStateProvider
) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
index 577137c..89fb12e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
@@ -20,35 +20,43 @@
import android.hardware.SensorManager
import android.os.Trace
import androidx.core.util.Consumer
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
import java.util.concurrent.Executor
+import javax.inject.Inject
-internal class HingeSensorAngleProvider(
+internal class HingeSensorAngleProvider
+@Inject
+constructor(
private val sensorManager: SensorManager,
- private val executor: Executor
-) :
- HingeAngleProvider {
+ @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor
+) : HingeAngleProvider {
private val sensorListener = HingeAngleSensorListener()
private val listeners: MutableList<Consumer<Float>> = arrayListOf()
var started = false
- override fun start() = executor.execute {
- if (started) return@execute
- Trace.beginSection("HingeSensorAngleProvider#start")
- val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)
- sensorManager.registerListener(
- sensorListener,
- sensor,
- SensorManager.SENSOR_DELAY_FASTEST
- )
- Trace.endSection()
- started = true
+ override fun start() {
+ singleThreadBgExecutor.execute {
+ if (started) return@execute
+ Trace.beginSection("HingeSensorAngleProvider#start")
+ val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)
+ sensorManager.registerListener(
+ sensorListener,
+ sensor,
+ SensorManager.SENSOR_DELAY_FASTEST
+ )
+ Trace.endSection()
+
+ started = true
+ }
}
- override fun stop() = executor.execute {
- if (!started) return@execute
- sensorManager.unregisterListener(sensorListener)
- started = false
+ override fun stop() {
+ singleThreadBgExecutor.execute {
+ if (!started) return@execute
+ sensorManager.unregisterListener(sensorListener)
+ started = false
+ }
}
override fun removeCallback(listener: Consumer<Float>) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 88492ed..35b5f1b 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2010,7 +2010,7 @@
@Override
public void hasFeatures(IAccountManagerResponse response,
- Account account, String[] features, String opPackageName) {
+ Account account, String[] features, int userId, String opPackageName) {
int callingUid = Binder.getCallingUid();
mAppOpsManager.checkPackage(callingUid, opPackageName);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -2018,12 +2018,22 @@
+ ", response " + response
+ ", features " + Arrays.toString(features)
+ ", caller's uid " + callingUid
+ + ", userId " + userId
+ ", pid " + Binder.getCallingPid());
}
Preconditions.checkArgument(account != null, "account cannot be null");
Preconditions.checkArgument(response != null, "response cannot be null");
Preconditions.checkArgument(features != null, "features cannot be null");
- int userId = UserHandle.getCallingUserId();
+
+ if (userId != UserHandle.getCallingUserId()
+ && callingUid != Process.SYSTEM_UID
+ && mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("User " + UserHandle.getCallingUserId()
+ + " trying to check account features for " + userId);
+ }
+
checkReadAccountsPermitted(callingUid, account.type, userId,
opPackageName);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 58ddd9c..39c7f30 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3785,12 +3785,13 @@
super.setVolumeIndexForAttributes_enforcePermission();
Objects.requireNonNull(attr, "attr must not be null");
- final int volumeGroup = getVolumeGroupIdForAttributes(attr);
+ int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
+ attr, /* fallbackOnDefault= */false);
if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
Log.e(TAG, ": no volume group found for attributes " + attr.toString());
return;
}
- final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+ VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
index/*val1*/, flags/*val2*/, callingPackage));
@@ -3798,7 +3799,7 @@
vgs.setVolumeIndex(index, flags);
// For legacy reason, propagate to all streams associated to this volume group
- for (final int groupedStream : vgs.getLegacyStreamTypes()) {
+ for (int groupedStream : vgs.getLegacyStreamTypes()) {
try {
ensureValidStreamType(groupedStream);
} catch (IllegalArgumentException e) {
@@ -3830,7 +3831,9 @@
super.getVolumeIndexForAttributes_enforcePermission();
Objects.requireNonNull(attr, "attr must not be null");
- final int volumeGroup = getVolumeGroupIdForAttributes(attr);
+ final int volumeGroup =
+ AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
+ attr, /* fallbackOnDefault= */false);
if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
throw new IllegalArgumentException("No volume group for attributes " + attr);
}
@@ -4380,31 +4383,6 @@
sendVolumeUpdate(streamType, oldIndex, index, flags, device);
}
-
-
- private int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) {
- Objects.requireNonNull(attributes, "attributes must not be null");
- int volumeGroupId = getVolumeGroupIdForAttributesInt(attributes);
- if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
- return volumeGroupId;
- }
- // The default volume group is the one hosted by default product strategy, i.e.
- // supporting Default Attributes
- return getVolumeGroupIdForAttributesInt(AudioProductStrategy.getDefaultAttributes());
- }
-
- private int getVolumeGroupIdForAttributesInt(@NonNull AudioAttributes attributes) {
- Objects.requireNonNull(attributes, "attributes must not be null");
- for (final AudioProductStrategy productStrategy :
- AudioProductStrategy.getAudioProductStrategies()) {
- int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes);
- if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
- return volumeGroupId;
- }
- }
- return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
- }
-
private void dispatchAbsoluteVolumeChanged(int streamType, AbsoluteVolumeDeviceInfo deviceInfo,
int index) {
VolumeInfo volumeInfo = deviceInfo.getMatchingVolumeInfoForStream(streamType);
@@ -4437,7 +4415,6 @@
}
}
-
// No ringer or zen muted stream volumes can be changed unless it'll exit dnd
private boolean volumeAdjustmentAllowedByDnd(int streamTypeAlias, int flags) {
switch (mNm.getZenMode()) {
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index dcc98e1..5b696c2 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -104,6 +104,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.config.appcloning.AppCloningDeviceConfigHelper;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BackgroundThread;
@@ -264,6 +265,8 @@
private final SyncLogger mLogger;
+ private final AppCloningDeviceConfigHelper mAppCloningDeviceConfigHelper;
+
private boolean isJobIdInUseLockedH(int jobId, List<JobInfo> pendingJobs) {
for (int i = 0, size = pendingJobs.size(); i < size; i++) {
JobInfo job = pendingJobs.get(i);
@@ -627,6 +630,7 @@
}, mSyncHandler);
mConstants = new SyncManagerConstants(context);
+ mAppCloningDeviceConfigHelper = AppCloningDeviceConfigHelper.getInstance(context);
IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
@@ -828,6 +832,18 @@
}
/**
+ * Check whether the feature flag controlling contacts sharing for clone profile is set. If
+ * true, the contact syncs for clone profile should be disabled.
+ *
+ * @return true/false if contact sharing is enabled/disabled
+ */
+ protected boolean isContactSharingAllowedForCloneProfile() {
+ // TODO(b/253449368): This method should also check for the config controlling
+ // all app-cloning features.
+ return mAppCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks();
+ }
+
+ /**
* Check if account sync should be disabled for the given user and provider.
* @param userInfo
* @param providerName
@@ -836,7 +852,9 @@
*/
@VisibleForTesting
protected boolean shouldDisableSyncForUser(UserInfo userInfo, String providerName) {
- if (userInfo == null || providerName == null) return false;
+ if (userInfo == null || providerName == null || !isContactSharingAllowedForCloneProfile()) {
+ return false;
+ }
return providerName.equals(ContactsContract.AUTHORITY)
&& !areContactWritesEnabledForUser(userInfo);
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index f3d8803..10c16b6 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -380,7 +380,12 @@
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
- // Do nothing.
+ // Do nothing but preserving the last IME requested visibility state.
+ final ImeTargetWindowState lastState =
+ getWindowStateOrNull(mService.mLastImeTargetWindow);
+ if (lastState != null) {
+ state.setRequestedImeVisible(lastState.mRequestedImeVisible);
+ }
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
if (isForwardNavigation) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 187de930..ba9e280 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -469,11 +469,11 @@
@GuardedBy("ImfLock.class")
private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) {
- if (getCurIntent() == null || conn == null) {
+ if (mCurIntent == null || conn == null) {
Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
return false;
}
- return mContext.bindServiceAsUser(getCurIntent(), conn, flags,
+ return mContext.bindServiceAsUser(mCurIntent, conn, flags,
new UserHandle(mSettings.getCurrentUserId()));
}
diff --git a/services/core/java/com/android/server/media/projection/TEST_MAPPING b/services/core/java/com/android/server/media/projection/TEST_MAPPING
new file mode 100644
index 0000000..a792498
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+ "presubmit": [
+ {
+ "name": "MediaProjectionTests",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 5ab9f38..69ea559 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -108,7 +108,7 @@
@VisibleForTesting
static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000;
@VisibleForTesting
- static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 50000;
+ static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 6000;
private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000;
private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000;
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 93a119c..7f7a234 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -291,8 +291,8 @@
rollbackTimeoutIntent.putExtra(
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_SESSION_ID,
sessionId);
- rollbackTimeoutIntent.addFlags(
- Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ rollbackTimeoutIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_FOREGROUND);
mPm.mContext.sendBroadcastAsUser(rollbackTimeoutIntent, UserHandle.SYSTEM,
android.Manifest.permission.PACKAGE_ROLLBACK_AGENT);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 12841a4..0685435 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2875,6 +2875,8 @@
newUserType = UserManager.USER_TYPE_FULL_DEMO;
} else if ("--ephemeral".equals(opt)) {
flags |= UserInfo.FLAG_EPHEMERAL;
+ } else if ("--for-testing".equals(opt)) {
+ flags |= UserInfo.FLAG_FOR_TESTING;
} else if ("--pre-create-only".equals(opt)) {
preCreateOnly = true;
} else if ("--user-type".equals(opt)) {
@@ -4269,8 +4271,8 @@
pw.println(" list users");
pw.println(" Lists the current users.");
pw.println("");
- pw.println(" create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]");
- pw.println(" [--guest] [--pre-create-only] [--user-type USER_TYPE] USER_NAME");
+ pw.println(" create-user [--profileOf USER_ID] [--managed] [--restricted] [--guest]");
+ pw.println(" [--user-type USER_TYPE] [--ephemeral] [--for-testing] [--pre-create-only] USER_NAME");
pw.println(" Create a new user with the given USER_NAME, printing the new user identifier");
pw.println(" of the user.");
// TODO(b/142482943): Consider fetching the list of user types from UMS.
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 7b15e76..1787116 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -513,4 +513,20 @@
* @see UserManager#isMainUser()
*/
public abstract @UserIdInt int getMainUserId();
+
+ /**
+ * Returns the id of the user which should be in the foreground after boot completes.
+ *
+ * <p>If a boot user has been provided by calling {@link UserManager#setBootUser}, the
+ * returned value will be whatever was specified, as long as that user exists and can be
+ * switched to.
+ *
+ * <p>Otherwise, in {@link UserManager#isHeadlessSystemUserMode() headless system user mode},
+ * this will be the user who was last in the foreground on this device. If there is no
+ * switchable user on the device, a new user will be created and its id will be returned.
+ *
+ * <p>In non-headless system user mode, the return value will be {@link UserHandle#USER_SYSTEM}.
+ */
+ public abstract @UserIdInt int getBootUser()
+ throws UserManager.CheckedUserOperationException;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a8cf8cb..175c11d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -21,6 +21,7 @@
import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
+import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
import android.Manifest;
import android.accounts.Account;
@@ -31,7 +32,6 @@
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
-import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
@@ -44,6 +44,7 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
@@ -127,11 +128,8 @@
import com.android.server.LockGuard;
import com.android.server.SystemService;
import com.android.server.am.UserState;
-import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
-import com.android.server.pm.UserManagerInternal.UserStartMode;
-import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
import com.android.server.storage.DeviceStorageMonitorInternal;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -254,7 +252,8 @@
| UserInfo.FLAG_RESTRICTED
| UserInfo.FLAG_GUEST
| UserInfo.FLAG_DEMO
- | UserInfo.FLAG_FULL;
+ | UserInfo.FLAG_FULL
+ | UserInfo.FLAG_FOR_TESTING;
@VisibleForTesting
static final int MIN_USER_ID = UserHandle.MIN_SECONDARY_USER_ID;
@@ -637,6 +636,9 @@
private final UserVisibilityMediator mUserVisibilityMediator;
+ @GuardedBy("mUsersLock")
+ private @UserIdInt int mBootUser = UserHandle.USER_NULL;
+
private static UserManagerService sInstance;
public static UserManagerService getInstance() {
@@ -936,6 +938,26 @@
}
@Override
+ public void setBootUser(@UserIdInt int userId) {
+ checkCreateUsersPermission("Set boot user");
+ synchronized (mUsersLock) {
+ // TODO(b/263381643): Change to EventLog.
+ Slogf.i(LOG_TAG, "setBootUser %d", userId);
+ mBootUser = userId;
+ }
+ }
+
+ @Override
+ public @UserIdInt int getBootUser() {
+ checkCreateUsersPermission("Get boot user");
+ try {
+ return mLocalService.getBootUser();
+ } catch (UserManager.CheckedUserOperationException e) {
+ throw e.toServiceSpecificException();
+ }
+ }
+
+ @Override
public int getPreviousFullUserToEnterForeground() {
checkQueryOrCreateUsersPermission("get previous user");
int previousUser = UserHandle.USER_NULL;
@@ -1569,6 +1591,8 @@
Slog.w(LOG_TAG, "System user instantiated at least " + number + " times");
}
name = getOwnerName();
+ } else if (orig.isMain()) {
+ name = getOwnerName();
} else if (orig.isGuest()) {
name = getGuestName();
}
@@ -4535,7 +4559,7 @@
UserHandle.USER_NULL, null);
if (userInfo == null) {
- throw new ServiceSpecificException(UserManager.USER_OPERATION_ERROR_UNKNOWN);
+ throw new ServiceSpecificException(USER_OPERATION_ERROR_UNKNOWN);
}
} catch (UserManager.CheckedUserOperationException e) {
throw e.toServiceSpecificException();
@@ -4664,7 +4688,7 @@
if (parent == null) {
throwCheckedUserOperationException(
"Cannot find user data for parent user " + parentId,
- UserManager.USER_OPERATION_ERROR_UNKNOWN);
+ USER_OPERATION_ERROR_UNKNOWN);
}
}
if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) {
@@ -4692,7 +4716,7 @@
&& !isCreationOverrideEnabled()) {
throwCheckedUserOperationException(
"Cannot add restricted profile - parent user must be system",
- UserManager.USER_OPERATION_ERROR_UNKNOWN);
+ USER_OPERATION_ERROR_UNKNOWN);
}
userId = getNextAvailableId();
@@ -5598,29 +5622,24 @@
// Also, add the UserHandle for mainline modules which can't use the @hide
// EXTRA_USER_HANDLE.
removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
- mContext.sendOrderedBroadcastAsUser(removedIntent, UserHandle.ALL,
- android.Manifest.permission.MANAGE_USERS,
-
- new BroadcastReceiver() {
+ getActivityManagerInternal().broadcastIntentWithCallback(removedIntent,
+ new IIntentReceiver.Stub() {
@Override
- public void onReceive(Context context, Intent intent) {
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
if (DBG) {
Slog.i(LOG_TAG,
"USER_REMOVED broadcast sent, cleaning up user data "
- + userId);
+ + userId);
}
- new Thread() {
- @Override
- public void run() {
- LocalServices.getService(ActivityManagerInternal.class)
- .onUserRemoved(userId);
- removeUserState(userId);
- }
- }.start();
+ new Thread(() -> {
+ getActivityManagerInternal().onUserRemoved(userId);
+ removeUserState(userId);
+ }).start();
}
},
-
- null, Activity.RESULT_OK, null, null);
+ new String[] {android.Manifest.permission.MANAGE_USERS},
+ UserHandle.USER_ALL, null, null, null);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6459,6 +6478,9 @@
if (DBG_ALLOCATION) {
pw.println(" System user allocations: " + mUser0Allocations.get());
}
+ synchronized (mUsersLock) {
+ pw.println(" Boot user: " + mBootUser);
+ }
pw.println();
pw.println("Number of listeners for");
@@ -6651,6 +6673,18 @@
return mLocalService.isUserInitialized(userId);
}
+ /**
+ * Creates a new user, intended to be the initial user on a device in headless system user mode.
+ */
+ private UserInfo createInitialUserForHsum() throws UserManager.CheckedUserOperationException {
+ final int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;
+
+ // Null name will be replaced with "Owner" on-demand to allow for localisation.
+ return createUserInternalUnchecked(/* name= */ null, UserManager.USER_TYPE_FULL_SECONDARY,
+ flags, UserHandle.USER_NULL, /* preCreate= */ false,
+ /* disallowedPackages= */ null, /* token= */ null);
+ }
+
private class LocalService extends UserManagerInternal {
@Override
public void setDevicePolicyUserRestrictions(@UserIdInt int originatingUserId,
@@ -7094,6 +7128,56 @@
return getMainUserIdUnchecked();
}
+ @Override
+ public @UserIdInt int getBootUser() throws UserManager.CheckedUserOperationException {
+ synchronized (mUsersLock) {
+ // TODO(b/242195409): On Automotive, block if boot user not provided.
+ if (mBootUser != UserHandle.USER_NULL) {
+ final UserData userData = mUsers.get(mBootUser);
+ if (userData != null && userData.info.supportsSwitchToByUser()) {
+ Slogf.i(LOG_TAG, "Using provided boot user: %d", mBootUser);
+ return mBootUser;
+ } else {
+ Slogf.w(LOG_TAG,
+ "Provided boot user cannot be switched to: %d", mBootUser);
+ }
+ }
+ }
+
+ if (isHeadlessSystemUserMode()) {
+ // Return the previous foreground user, if there is one.
+ final int previousUser = getPreviousFullUserToEnterForeground();
+ if (previousUser != UserHandle.USER_NULL) {
+ Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
+ return previousUser;
+ }
+ // No previous user. Return the first switchable user if there is one.
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ final UserData userData = mUsers.valueAt(i);
+ if (userData.info.supportsSwitchToByUser()) {
+ int firstSwitchable = userData.info.id;
+ Slogf.i(LOG_TAG,
+ "Boot user is first switchable user %d", firstSwitchable);
+ return firstSwitchable;
+ }
+ }
+ }
+ // No switchable users. Create the initial user.
+ final UserInfo newInitialUser = createInitialUserForHsum();
+ if (newInitialUser == null) {
+ throw new UserManager.CheckedUserOperationException(
+ "Initial user creation failed", USER_OPERATION_ERROR_UNKNOWN);
+ }
+ Slogf.i(LOG_TAG,
+ "No switchable users. Boot user is new user %d", newInitialUser.id);
+ return newInitialUser.id;
+ }
+ // Not HSUM, return system user.
+ return UserHandle.USER_SYSTEM;
+ }
+
} // class LocalService
@@ -7113,7 +7197,7 @@
+ restriction + " is enabled.";
Slog.w(LOG_TAG, errorMessage);
throw new UserManager.CheckedUserOperationException(errorMessage,
- UserManager.USER_OPERATION_ERROR_UNKNOWN);
+ USER_OPERATION_ERROR_UNKNOWN);
}
}
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index a54f526..8ec6241 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -233,7 +233,8 @@
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_SESSION_ID,
mSessionId);
enableRollbackIntent.setType(PACKAGE_MIME_TYPE);
- enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_RECEIVER_FOREGROUND);
// Allow the broadcast to be sent before boot complete.
// This is needed when committing the apk part of a staged
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
new file mode 100644
index 0000000..a380dea
--- /dev/null
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -0,0 +1,137 @@
+/*
+ * 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.wallpaper;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
+import android.os.Debug;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.util.function.Consumer;
+/**
+ * Internal class used to store all the display data relevant to the wallpapers
+ */
+class WallpaperDisplayHelper {
+
+ @VisibleForTesting
+ static final class DisplayData {
+ int mWidth = -1;
+ int mHeight = -1;
+ final Rect mPadding = new Rect(0, 0, 0, 0);
+ final int mDisplayId;
+ DisplayData(int displayId) {
+ mDisplayId = displayId;
+ }
+ }
+
+ private static final String TAG = WallpaperDisplayHelper.class.getSimpleName();
+ private final SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
+ private final DisplayManager mDisplayManager;
+ private final WindowManagerInternal mWindowManagerInternal;
+
+ WallpaperDisplayHelper(
+ DisplayManager displayManager,
+ WindowManagerInternal windowManagerInternal) {
+ mDisplayManager = displayManager;
+ mWindowManagerInternal = windowManagerInternal;
+ }
+
+ DisplayData getDisplayDataOrCreate(int displayId) {
+ DisplayData wpdData = mDisplayDatas.get(displayId);
+ if (wpdData == null) {
+ wpdData = new DisplayData(displayId);
+ ensureSaneWallpaperDisplaySize(wpdData, displayId);
+ mDisplayDatas.append(displayId, wpdData);
+ }
+ return wpdData;
+ }
+
+ void removeDisplayData(int displayId) {
+ mDisplayDatas.remove(displayId);
+ }
+
+ void ensureSaneWallpaperDisplaySize(DisplayData wpdData, int displayId) {
+ // We always want to have some reasonable width hint.
+ final int baseSize = getMaximumSizeDimension(displayId);
+ if (wpdData.mWidth < baseSize) {
+ wpdData.mWidth = baseSize;
+ }
+ if (wpdData.mHeight < baseSize) {
+ wpdData.mHeight = baseSize;
+ }
+ }
+
+ int getMaximumSizeDimension(int displayId) {
+ Display display = mDisplayManager.getDisplay(displayId);
+ if (display == null) {
+ Slog.w(TAG, "Invalid displayId=" + displayId + " " + Debug.getCallers(4));
+ display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+ }
+ return display.getMaximumSizeDimension();
+ }
+
+ void forEachDisplayData(Consumer<DisplayData> action) {
+ for (int i = mDisplayDatas.size() - 1; i >= 0; i--) {
+ final DisplayData wpdData = mDisplayDatas.valueAt(i);
+ action.accept(wpdData);
+ }
+ }
+
+ Display[] getDisplays() {
+ return mDisplayManager.getDisplays();
+ }
+
+ DisplayInfo getDisplayInfo(int displayId) {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ mDisplayManager.getDisplay(displayId).getDisplayInfo(displayInfo);
+ return displayInfo;
+ }
+
+ boolean isUsableDisplay(int displayId, int clientUid) {
+ return isUsableDisplay(mDisplayManager.getDisplay(displayId), clientUid);
+ }
+
+ boolean isUsableDisplay(Display display, int clientUid) {
+ if (display == null || !display.hasAccess(clientUid)) {
+ return false;
+ }
+ final int displayId = display.getDisplayId();
+ if (displayId == DEFAULT_DISPLAY) {
+ return true;
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mWindowManagerInternal.shouldShowSystemDecorOnDisplay(displayId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ boolean isValidDisplay(int displayId) {
+ return mDisplayManager.getDisplay(displayId) != null;
+ }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 6edfebf..075bac1 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -32,6 +32,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wallpaper.WallpaperDisplayHelper.DisplayData;
import static com.android.server.wallpaper.WallpaperUtils.RECORD_FILE;
import static com.android.server.wallpaper.WallpaperUtils.RECORD_LOCK_FILE;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
@@ -79,7 +80,6 @@
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Debug;
import android.os.FileObserver;
import android.os.FileUtils;
import android.os.Handler;
@@ -609,10 +609,9 @@
boolean success = false;
// Only generate crop for default display.
- final DisplayData wpData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ final DisplayData wpData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
final Rect cropHint = new Rect(wallpaper.cropHint);
- final DisplayInfo displayInfo = new DisplayInfo();
- mDisplayManager.getDisplay(DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
+ final DisplayInfo displayInfo = mWallpaperDisplayHelper.getDisplayInfo(DEFAULT_DISPLAY);
if (DEBUG) {
Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
@@ -829,7 +828,8 @@
private final MyPackageMonitor mMonitor;
private final AppOpsManager mAppOpsManager;
- private final DisplayManager mDisplayManager;
+ // TODO("b/264637309") probably move this in WallpaperDisplayUtils,
+ // after logic is changed for the lockscreen lwp project
private final DisplayManager.DisplayListener mDisplayListener =
new DisplayManager.DisplayListener() {
@@ -848,12 +848,12 @@
targetWallpaper = mFallbackWallpaper;
}
if (targetWallpaper == null) return;
- WallpaperConnection.DisplayConnector connector =
+ DisplayConnector connector =
targetWallpaper.connection.getDisplayConnectorOrCreate(displayId);
if (connector == null) return;
connector.disconnectLocked();
targetWallpaper.connection.removeDisplayConnector(displayId);
- removeDisplayData(displayId);
+ mWallpaperDisplayHelper.removeDisplayData(displayId);
}
for (int i = mColorsChangedListeners.size() - 1; i >= 0; i--) {
final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> callbacks =
@@ -904,8 +904,6 @@
private final SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>();
private final SparseArray<WallpaperData> mLockWallpaperMap = new SparseArray<WallpaperData>();
- private SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
-
protected WallpaperData mFallbackWallpaper;
private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
@@ -914,57 +912,7 @@
private LocalColorRepository mLocalColorRepo = new LocalColorRepository();
@VisibleForTesting
- static final class DisplayData {
- int mWidth = -1;
- int mHeight = -1;
- final Rect mPadding = new Rect(0, 0, 0, 0);
- final int mDisplayId;
-
- DisplayData(int displayId) {
- mDisplayId = displayId;
- }
- }
-
- private void removeDisplayData(int displayId) {
- mDisplayDatas.remove(displayId);
- }
-
- private DisplayData getDisplayDataOrCreate(int displayId) {
- DisplayData wpdData = mDisplayDatas.get(displayId);
- if (wpdData == null) {
- wpdData = new DisplayData(displayId);
- ensureSaneWallpaperDisplaySize(wpdData, displayId);
- mDisplayDatas.append(displayId, wpdData);
- }
- return wpdData;
- }
-
- private void ensureSaneWallpaperDisplaySize(DisplayData wpdData, int displayId) {
- // We always want to have some reasonable width hint.
- final int baseSize = getMaximumSizeDimension(displayId);
- if (wpdData.mWidth < baseSize) {
- wpdData.mWidth = baseSize;
- }
- if (wpdData.mHeight < baseSize) {
- wpdData.mHeight = baseSize;
- }
- }
-
- private int getMaximumSizeDimension(int displayId) {
- Display display = mDisplayManager.getDisplay(displayId);
- if (display == null) {
- Slog.w(TAG, "Invalid displayId=" + displayId + " " + Debug.getCallers(4));
- display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
- }
- return display.getMaximumSizeDimension();
- }
-
- void forEachDisplayData(Consumer<DisplayData> action) {
- for (int i = mDisplayDatas.size() - 1; i >= 0; i--) {
- final DisplayData wpdData = mDisplayDatas.valueAt(i);
- action.accept(wpdData);
- }
- }
+ final WallpaperDisplayHelper mWallpaperDisplayHelper;
private boolean supportsMultiDisplay(WallpaperConnection connection) {
if (connection != null) {
@@ -993,7 +941,7 @@
}
} else {
fallbackConnection.appendConnectorWithCondition(display ->
- fallbackConnection.isUsableDisplay(display)
+ mWallpaperDisplayHelper.isUsableDisplay(display, fallbackConnection.mClientUid)
&& display.getDisplayId() != DEFAULT_DISPLAY
&& !fallbackConnection.containsDisplay(display.getDisplayId()));
fallbackConnection.forEachDisplayConnector(connector -> {
@@ -1004,84 +952,87 @@
}
}
- class WallpaperConnection extends IWallpaperConnection.Stub
- implements ServiceConnection {
+ /**
+ * Collect needed info for a display.
+ */
+ @VisibleForTesting
+ final class DisplayConnector {
+ final int mDisplayId;
+ final Binder mToken = new Binder();
+ IWallpaperEngine mEngine;
+ boolean mDimensionsChanged;
+ boolean mPaddingChanged;
- /**
- * Collect needed info for a display.
- */
- @VisibleForTesting
- final class DisplayConnector {
- final int mDisplayId;
- final Binder mToken = new Binder();
- IWallpaperEngine mEngine;
- boolean mDimensionsChanged;
- boolean mPaddingChanged;
+ DisplayConnector(int displayId) {
+ mDisplayId = displayId;
+ }
- DisplayConnector(int displayId) {
- mDisplayId = displayId;
- }
-
- void ensureStatusHandled() {
- final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
- if (mDimensionsChanged) {
- try {
- mEngine.setDesiredSize(wpdData.mWidth, wpdData.mHeight);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to set wallpaper dimensions", e);
- }
- mDimensionsChanged = false;
- }
- if (mPaddingChanged) {
- try {
- mEngine.setDisplayPadding(wpdData.mPadding);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to set wallpaper padding", e);
- }
- mPaddingChanged = false;
- }
- }
-
- void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) {
- if (connection.mService == null) {
- Slog.w(TAG, "WallpaperService is not connected yet");
- return;
- }
- TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
- t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent);
- if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
- mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
- null /* options */);
- final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
+ void ensureStatusHandled() {
+ final DisplayData wpdData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
+ if (mDimensionsChanged) {
try {
- connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
- wpdData.mWidth, wpdData.mHeight,
- wpdData.mPadding, mDisplayId, mWallpaper.mWhich);
+ mEngine.setDesiredSize(wpdData.mWidth, wpdData.mHeight);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed attaching wallpaper on display", e);
- if (wallpaper != null && !wallpaper.wallpaperUpdating
- && connection.getConnectedEngineSize() == 0) {
- bindWallpaperComponentLocked(null /* componentName */, false /* force */,
- false /* fromUser */, wallpaper, null /* reply */);
- }
+ Slog.w(TAG, "Failed to set wallpaper dimensions", e);
}
- t.traceEnd();
+ mDimensionsChanged = false;
}
-
- void disconnectLocked() {
- if (DEBUG) Slog.v(TAG, "Removing window token: " + mToken);
- mWindowManagerInternal.removeWindowToken(mToken, false/* removeWindows */,
- mDisplayId);
+ if (mPaddingChanged) {
try {
- if (mEngine != null) {
- mEngine.destroy();
- }
+ mEngine.setDisplayPadding(wpdData.mPadding);
} catch (RemoteException e) {
+ Slog.w(TAG, "Failed to set wallpaper padding", e);
}
- mEngine = null;
+ mPaddingChanged = false;
}
}
+ void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) {
+ if (connection.mService == null) {
+ Slog.w(TAG, "WallpaperService is not connected yet");
+ return;
+ }
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent);
+ if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
+ mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
+ null /* options */);
+ final DisplayData wpdData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
+ try {
+ connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
+ wpdData.mWidth, wpdData.mHeight,
+ wpdData.mPadding, mDisplayId, wallpaper.mWhich);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed attaching wallpaper on display", e);
+ if (wallpaper != null && !wallpaper.wallpaperUpdating
+ && connection.getConnectedEngineSize() == 0) {
+ bindWallpaperComponentLocked(null /* componentName */, false /* force */,
+ false /* fromUser */, wallpaper, null /* reply */);
+ }
+ }
+ t.traceEnd();
+ }
+
+ void disconnectLocked() {
+ if (DEBUG) Slog.v(TAG, "Removing window token: " + mToken);
+ mWindowManagerInternal.removeWindowToken(mToken, false/* removeWindows */,
+ mDisplayId);
+ try {
+ if (mEngine != null) {
+ mEngine.destroy();
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Engine.destroy() threw a RemoteException");
+ }
+ mEngine = null;
+ }
+ }
+
+ class WallpaperConnection extends IWallpaperConnection.Stub
+ implements ServiceConnection {
+
/**
* A map for each display.
* Use {@link #getDisplayConnectorOrCreate(int displayId)} to ensure the display is usable.
@@ -1132,7 +1083,8 @@
if (!mWallpaper.equals(mFallbackWallpaper)) {
if (supportsMultiDisplay(this)) {
// The system wallpaper is image wallpaper or it can supports multiple displays.
- appendConnectorWithCondition(this::isUsableDisplay);
+ appendConnectorWithCondition(display ->
+ mWallpaperDisplayHelper.isUsableDisplay(display, mClientUid));
} else {
// The system wallpaper does not support multiple displays, so just attach it on
// default display.
@@ -1143,37 +1095,18 @@
}
private void appendConnectorWithCondition(Predicate<Display> tester) {
- final Display[] displays = mDisplayManager.getDisplays();
+ final Display[] displays = mWallpaperDisplayHelper.getDisplays();
for (Display display : displays) {
if (tester.test(display)) {
final int displayId = display.getDisplayId();
final DisplayConnector connector = mDisplayConnector.get(displayId);
if (connector == null) {
- mDisplayConnector.append(displayId,
- new DisplayConnector(displayId));
+ mDisplayConnector.append(displayId, new DisplayConnector(displayId));
}
}
}
}
- @VisibleForTesting
- boolean isUsableDisplay(Display display) {
- if (display == null || !display.hasAccess(mClientUid)) {
- return false;
- }
- final int displayId = display.getDisplayId();
- if (displayId == DEFAULT_DISPLAY) {
- return true;
- }
-
- final long ident = Binder.clearCallingIdentity();
- try {
- return mWindowManagerInternal.shouldShowSystemDecorOnDisplay(displayId);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
void forEachDisplayConnector(Consumer<DisplayConnector> action) {
for (int i = mDisplayConnector.size() - 1; i >= 0; i--) {
final DisplayConnector connector = mDisplayConnector.valueAt(i);
@@ -1193,8 +1126,7 @@
DisplayConnector getDisplayConnectorOrCreate(int displayId) {
DisplayConnector connector = mDisplayConnector.get(displayId);
if (connector == null) {
- final Display display = mDisplayManager.getDisplay(displayId);
- if (isUsableDisplay(display)) {
+ if (mWallpaperDisplayHelper.isUsableDisplay(displayId, mClientUid)) {
connector = new DisplayConnector(displayId);
mDisplayConnector.append(displayId, connector);
}
@@ -1633,8 +1565,9 @@
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mIPackageManager = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- mDisplayManager = mContext.getSystemService(DisplayManager.class);
- mDisplayManager.registerDisplayListener(mDisplayListener, null /* handler */);
+ DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+ dm.registerDisplayListener(mDisplayListener, null /* handler */);
+ mWallpaperDisplayHelper = new WallpaperDisplayHelper(dm, mWindowManagerInternal);
mActivityManager = mContext.getSystemService(ActivityManager.class);
mMonitor = new MyPackageMonitor();
mColorsChangedListeners = new SparseArray<>();
@@ -2084,10 +2017,6 @@
return false;
}
- private boolean isValidDisplay(int displayId) {
- return mDisplayManager.getDisplay(displayId) != null;
- }
-
/**
* Sets the dimension hint for the wallpaper. These hints indicate the desired
* minimum width and height for the wallpaper in a particular display.
@@ -2110,18 +2039,18 @@
throw new IllegalArgumentException("width and height must be > 0");
}
- if (!isValidDisplay(displayId)) {
+ if (!mWallpaperDisplayHelper.isValidDisplay(displayId)) {
throw new IllegalArgumentException("Cannot find display with id=" + displayId);
}
- final DisplayData wpdData = getDisplayDataOrCreate(displayId);
+ final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(displayId);
if (width != wpdData.mWidth || height != wpdData.mHeight) {
wpdData.mWidth = width;
wpdData.mHeight = height;
if (displayId == DEFAULT_DISPLAY) saveSettingsLocked(userId);
if (mCurrentUserId != userId) return; // Don't change the properties now
if (wallpaper.connection != null) {
- final WallpaperConnection.DisplayConnector connector = wallpaper.connection
+ final DisplayConnector connector = wallpaper.connection
.getDisplayConnectorOrCreate(displayId);
final IWallpaperEngine engine = connector != null ? connector.mEngine : null;
if (engine != null) {
@@ -2146,12 +2075,13 @@
*/
public int getWidthHint(int displayId) throws RemoteException {
synchronized (mLock) {
- if (!isValidDisplay(displayId)) {
+ if (!mWallpaperDisplayHelper.isValidDisplay(displayId)) {
throw new IllegalArgumentException("Cannot find display with id=" + displayId);
}
WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
if (wallpaper != null) {
- final DisplayData wpdData = getDisplayDataOrCreate(displayId);
+ final DisplayData wpdData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(displayId);
return wpdData.mWidth;
} else {
return 0;
@@ -2164,12 +2094,13 @@
*/
public int getHeightHint(int displayId) throws RemoteException {
synchronized (mLock) {
- if (!isValidDisplay(displayId)) {
+ if (!mWallpaperDisplayHelper.isValidDisplay(displayId)) {
throw new IllegalArgumentException("Cannot find display with id=" + displayId);
}
WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
if (wallpaper != null) {
- final DisplayData wpdData = getDisplayDataOrCreate(displayId);
+ final DisplayData wpdData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(displayId);
return wpdData.mHeight;
} else {
return 0;
@@ -2186,7 +2117,7 @@
return;
}
synchronized (mLock) {
- if (!isValidDisplay(displayId)) {
+ if (!mWallpaperDisplayHelper.isValidDisplay(displayId)) {
throw new IllegalArgumentException("Cannot find display with id=" + displayId);
}
int userId = UserHandle.getCallingUserId();
@@ -2195,7 +2126,7 @@
throw new IllegalArgumentException("padding must be positive: " + padding);
}
- int maxSize = getMaximumSizeDimension(displayId);
+ int maxSize = mWallpaperDisplayHelper.getMaximumSizeDimension(displayId);
final int paddingWidth = padding.left + padding.right;
final int paddingHeight = padding.top + padding.bottom;
@@ -2208,13 +2139,13 @@
+ " exceeds max height " + maxSize);
}
- final DisplayData wpdData = getDisplayDataOrCreate(displayId);
+ final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(displayId);
if (!padding.equals(wpdData.mPadding)) {
wpdData.mPadding.set(padding);
if (displayId == DEFAULT_DISPLAY) saveSettingsLocked(userId);
if (mCurrentUserId != userId) return; // Don't change the properties now
if (wallpaper.connection != null) {
- final WallpaperConnection.DisplayConnector connector = wallpaper.connection
+ final DisplayConnector connector = wallpaper.connection
.getDisplayConnectorOrCreate(displayId);
final IWallpaperEngine engine = connector != null ? connector.mEngine : null;
if (engine != null) {
@@ -2268,7 +2199,8 @@
return null;
}
// Only for default display.
- final DisplayData wpdData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ final DisplayData wpdData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
try {
if (outParams != null) {
outParams.putInt("width", wpdData.mWidth);
@@ -3155,8 +3087,7 @@
Slog.w(TAG, "Failed detaching wallpaper service ", e);
}
mContext.unbindService(wallpaper.connection);
- wallpaper.connection.forEachDisplayConnector(
- WallpaperConnection.DisplayConnector::disconnectLocked);
+ wallpaper.connection.forEachDisplayConnector(DisplayConnector::disconnectLocked);
wallpaper.connection.mService = null;
wallpaper.connection.mDisplayConnector.clear();
@@ -3286,7 +3217,7 @@
return;
}
if (supportsMultiDisplay(mLastWallpaper.connection)) {
- final WallpaperConnection.DisplayConnector connector =
+ final DisplayConnector connector =
mLastWallpaper.connection.getDisplayConnectorOrCreate(displayId);
if (connector == null) return;
connector.connectLocked(mLastWallpaper.connection, mLastWallpaper);
@@ -3295,7 +3226,7 @@
// System wallpaper does not support multiple displays, attach this display to
// the fallback wallpaper.
if (mFallbackWallpaper != null) {
- final WallpaperConnection.DisplayConnector connector = mFallbackWallpaper
+ final DisplayConnector connector = mFallbackWallpaper
.connection.getDisplayConnectorOrCreate(displayId);
if (connector == null) return;
connector.connectLocked(mFallbackWallpaper.connection, mFallbackWallpaper);
@@ -3352,7 +3283,7 @@
if (DEBUG) {
Slog.v(TAG, "writeWallpaperAttributes id=" + wallpaper.wallpaperId);
}
- final DisplayData wpdData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
out.startTag(null, tag);
out.attributeInt(null, "id", wallpaper.wallpaperId);
out.attributeInt(null, "width", wpdData.mWidth);
@@ -3536,7 +3467,7 @@
initializeFallbackWallpaper();
}
boolean success = false;
- final DisplayData wpdData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
try {
stream = new FileInputStream(file);
TypedXmlPullParser parser = Xml.resolvePullParser(stream);
@@ -3613,7 +3544,7 @@
}
}
- ensureSaneWallpaperDisplaySize(wpdData, DEFAULT_DISPLAY);
+ mWallpaperDisplayHelper.ensureSaneWallpaperDisplaySize(wpdData, DEFAULT_DISPLAY);
ensureSaneWallpaperData(wallpaper);
WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
if (lockWallpaper != null) {
@@ -3653,7 +3584,7 @@
wallpaper.wallpaperId = makeWallpaperIdLocked();
}
- final DisplayData wpData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ final DisplayData wpData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
if (!keepDimensionHints) {
wpData.mWidth = parser.getAttributeInt(null, "width");
@@ -3866,7 +3797,7 @@
pw.print(" User "); pw.print(wallpaper.userId);
pw.print(": id="); pw.println(wallpaper.wallpaperId);
pw.println(" Display state:");
- forEachDisplayData(wpSize -> {
+ mWallpaperDisplayHelper.forEachDisplayData(wpSize -> {
pw.print(" displayId=");
pw.println(wpSize.mDisplayId);
pw.print(" mWidth=");
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index af135b7..9e258cb 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -20,6 +20,7 @@
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
+import static android.view.ViewProtoEnums.DISPLAY_STATE_OFF;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTENT_RECORDING;
@@ -317,6 +318,11 @@
if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
mRecordedWindowContainer.asTask().isVisibleRequested());
+ } else {
+ int currentDisplayState =
+ mRecordedWindowContainer.asDisplayContent().getDisplay().getState();
+ mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+ currentDisplayState != DISPLAY_STATE_OFF);
}
// No need to clean up. In SurfaceFlinger, parents hold references to their children. The
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f47c330..fa6fa53 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1092,13 +1092,13 @@
// (ACTION_DATE_CHANGED), or when manual clock adjustment is made
// (ACTION_TIME_CHANGED)
updateSystemUpdateFreezePeriodsRecord(/* saveIfChanged */ true);
- final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
+ final int userId = getManagedUserId(mUserManager.getMainUser().getIdentifier());
if (userId >= 0) {
updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
}
} else if (ACTION_PROFILE_OFF_DEADLINE.equals(action)) {
Slogf.i(LOG_TAG, "Profile off deadline alarm was triggered");
- final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
+ final int userId = getManagedUserId(mUserManager.getMainUser().getIdentifier());
if (userId >= 0) {
updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
} else {
@@ -8598,9 +8598,20 @@
Preconditions.checkArgument(admin != null);
final CallerIdentity caller = getCallerIdentity();
- // Cannot be called while holding the lock:
- final boolean hasIncompatibleAccountsOrNonAdb =
- hasIncompatibleAccountsOrNonAdbNoLock(caller, userId, admin);
+
+ boolean hasIncompatibleAccountsOrNonAdb =
+ !isAdb(caller) || hasIncompatibleAccountsOnAnyUser();
+
+ if (!hasIncompatibleAccountsOrNonAdb) {
+ synchronized (getLockObject()) {
+ if (!isAdminTestOnlyLocked(admin, userId) && hasAccountsOnAnyUser()) {
+ Slogf.w(LOG_TAG,
+ "Non test-only owner can't be installed with existing accounts.");
+ return false;
+ }
+ }
+ }
+
synchronized (getLockObject()) {
enforceCanSetDeviceOwnerLocked(caller, admin, userId, hasIncompatibleAccountsOrNonAdb);
Preconditions.checkArgument(isPackageInstalledForUser(admin.getPackageName(), userId),
@@ -14746,14 +14757,10 @@
if (isAdb) {
// If shell command runs after user setup completed check device status. Otherwise, OK.
if (mIsWatch || hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
- // In non-headless system user mode, DO can be setup only if
- // there's no non-system user.
- // In headless system user mode, DO can be setup only if there are
- // two users: the headless system user and the foreground user.
- // If there could be multiple foreground users, this constraint should be modified.
+ // DO can be setup only if there are no users which are neither created by default
+ // nor marked as FOR_TESTING
- int maxNumberOfExistingUsers = isHeadlessSystemUserMode ? 2 : 1;
- if (mUserManager.getUserCount() > maxNumberOfExistingUsers) {
+ if (nonTestNonPrecreatedUsersExist()) {
return STATUS_NONSYSTEM_USER_EXISTS;
}
@@ -14783,6 +14790,17 @@
}
}
+ /**
+ * True if there are any users on the device which were not setup by default (1 usually, 2 for
+ * devices with a headless system user) and also are not marked as FOR_TESTING.
+ */
+ private boolean nonTestNonPrecreatedUsersExist() {
+ int allowedUsers = UserManager.isHeadlessSystemUserMode() ? 2 : 1;
+ return mUserManagerInternal.getUsers(/* excludeDying= */ true).stream()
+ .filter(u -> !u.isForTesting())
+ .count() > allowedUsers;
+ }
+
private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) {
synchronized (getLockObject()) {
final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
@@ -16054,8 +16072,10 @@
wtfIfInLock();
return mInjector.binderWithCleanCallingIdentity(() -> {
- final AccountManager am = AccountManager.get(mContext);
- final Account accounts[] = am.getAccountsAsUser(userId);
+ AccountManager am =
+ mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0)
+ .getSystemService(AccountManager.class);
+ Account[] accounts = am.getAccounts();
if (accounts.length == 0) {
return false;
}
@@ -18169,8 +18189,10 @@
.addAction(turnProfileOnButton)
.addExtras(extras)
.build();
- mInjector.getNotificationManager().notify(
- SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification);
+
+ mInjector.getNotificationManager().notifyAsUser(
+ null, SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification,
+ UserHandle.of(getProfileParentId(profileUserId)));
}
private String getPersonalAppSuspensionButtonText() {
@@ -19537,6 +19559,14 @@
}
@Override
+ public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState() {
+ Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_ROLE_HOLDERS));
+ setBypassDevicePolicyManagementRoleQualificationStateInternal(
+ /* currentRoleHolder= */ null, /* allowBypass= */ false);
+ }
+
+ @Override
public boolean shouldAllowBypassingDevicePolicyManagementRoleQualification() {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
android.Manifest.permission.MANAGE_ROLE_HOLDERS));
@@ -19550,15 +19580,51 @@
}
private boolean shouldAllowBypassingDevicePolicyManagementRoleQualificationInternal() {
- if (mUserManager.getUserCount() > 1) {
+ if (nonTestNonPrecreatedUsersExist()) {
return false;
}
- AccountManager am = AccountManager.get(mContext);
- Account[] accounts = am.getAccounts();
- if (accounts.length == 0) {
- return true;
+
+
+ return !hasIncompatibleAccountsOnAnyUser();
+ }
+
+ private boolean hasAccountsOnAnyUser() {
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ for (UserInfo user : mUserManagerInternal.getUsers(/* excludeDying= */ true)) {
+ AccountManager am = mContext.createContextAsUser(
+ UserHandle.of(user.id), /* flags= */ 0)
+ .getSystemService(AccountManager.class);
+ Account[] accounts = am.getAccounts();
+ if (accounts.length != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
}
- return !hasIncompatibleAccounts(am, accounts);
+ }
+
+ private boolean hasIncompatibleAccountsOnAnyUser() {
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ for (UserInfo user : mUserManagerInternal.getUsers(/* excludeDying= */ true)) {
+ AccountManager am = mContext.createContextAsUser(
+ UserHandle.of(user.id), /* flags= */ 0)
+ .getSystemService(AccountManager.class);
+ Account[] accounts = am.getAccounts();
+
+ if (hasIncompatibleAccounts(am, accounts)) {
+ return true;
+ }
+ }
+
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
}
private void setBypassDevicePolicyManagementRoleQualificationStateInternal(
diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java
index deebfc7..3d71739 100644
--- a/services/java/com/android/server/BootUserInitializer.java
+++ b/services/java/com/android/server/BootUserInitializer.java
@@ -17,7 +17,6 @@
import android.annotation.UserIdInt;
import android.content.ContentResolver;
-import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -27,8 +26,6 @@
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
-import java.util.List;
-
/**
* Class responsible for booting the device in the proper user on headless system user mode.
*
@@ -56,50 +53,18 @@
// this class or the setup wizard app
provisionHeadlessSystemUser();
- UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
- t.traceBegin("get-existing-users");
- List<UserInfo> existingUsers = um.getUsers(/* excludeDying= */ true);
- t.traceEnd();
-
- Slogf.d(TAG, "%d existing users", existingUsers.size());
-
- int initialUserId = UserHandle.USER_NULL;
-
- for (int i = 0; i < existingUsers.size(); i++) {
- UserInfo user = existingUsers.get(i);
- if (DEBUG) {
- Slogf.d(TAG, "User at position %d: %s", i, user.toFullString());
- }
- if (user.id != UserHandle.USER_SYSTEM && user.isFull()) {
- if (DEBUG) {
- Slogf.d(TAG, "Found initial user: %d", user.id);
- }
- initialUserId = user.id;
- break;
- }
- }
-
- if (initialUserId == UserHandle.USER_NULL) {
- Slogf.d(TAG, "Creating initial user");
- t.traceBegin("create-initial-user");
- try {
- int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;
- // TODO(b/204091126): proper name for user
- UserInfo newUser = um.createUserEvenWhenDisallowed("Real User",
- UserManager.USER_TYPE_FULL_SECONDARY, flags,
- /* disallowedPackages= */ null, /* token= */ null);
- Slogf.i(TAG, "Created initial user: %s", newUser.toFullString());
- initialUserId = newUser.id;
- } catch (Exception e) {
- Slogf.wtf(TAG, "failed to created initial user", e);
- return;
- } finally {
- t.traceEnd(); // create-initial-user
- }
- }
-
unlockSystemUser(t);
- switchToInitialUser(initialUserId);
+
+ try {
+ t.traceBegin("getBootUser");
+ int bootUser = LocalServices.getService(UserManagerInternal.class).getBootUser();
+ t.traceEnd();
+ t.traceBegin("switchToBootUser-" + bootUser);
+ switchToBootUser(bootUser);
+ t.traceEnd();
+ } catch (UserManager.CheckedUserOperationException e) {
+ Slogf.wtf(TAG, "Failed to created boot user", e);
+ }
}
/* TODO(b/261791491): STOPSHIP - SUW should be responsible for this. */
@@ -152,12 +117,12 @@
}
}
- private void switchToInitialUser(@UserIdInt int initialUserId) {
- Slogf.i(TAG, "Switching to initial user %d", initialUserId);
- boolean started = mAms.startUserInForegroundWithListener(initialUserId,
+ private void switchToBootUser(@UserIdInt int bootUserId) {
+ Slogf.i(TAG, "Switching to boot user %d", bootUserId);
+ boolean started = mAms.startUserInForegroundWithListener(bootUserId,
/* unlockListener= */ null);
if (!started) {
- Slogf.wtf(TAG, "Failed to start user %d in foreground", initialUserId);
+ Slogf.wtf(TAG, "Failed to start user %d in foreground", bootUserId);
}
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 1367af8..f13de12 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -20,25 +20,31 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
import androidx.test.annotation.UiThreadTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.internal.widget.LockSettingsInternal;
import com.android.server.ExtendedMockitoTestCase;
import com.android.server.LocalServices;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerService.UserData;
+import com.android.server.storage.DeviceStorageMonitorInternal;
import org.junit.After;
import org.junit.Before;
@@ -86,6 +92,10 @@
private @Mock PackageManagerService mMockPms;
private @Mock UserDataPreparer mMockUserDataPreparer;
private @Mock ActivityManagerInternal mActivityManagerInternal;
+ private @Mock DeviceStorageMonitorInternal mDeviceStorageMonitorInternal;
+ private @Mock StorageManager mStorageManager;
+ private @Mock LockSettingsInternal mLockSettingsInternal;
+ private @Mock PackageManagerInternal mPackageManagerInternal;
/**
* Reference to the {@link UserManagerService} being tested.
@@ -101,7 +111,8 @@
protected void initializeSession(StaticMockitoSessionBuilder builder) {
builder
.spyStatic(UserManager.class)
- .spyStatic(LocalServices.class);
+ .spyStatic(LocalServices.class)
+ .mockStatic(Settings.Global.class);
}
@Before
@@ -112,6 +123,14 @@
// Called when WatchedUserStates is constructed
doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
+ // Called when creating new users
+ when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
+ mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
+ when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
+ mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
+ mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
+ doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
+
// Must construct UserManagerService in the UiThread
mUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
mPackagesLock, mRealContext.getDataDir(), mUsers);
@@ -223,6 +242,87 @@
.that(mUms.isUserRunning(PROFILE_USER_ID)).isFalse();
}
+ @Test
+ public void testSetBootUser_SuppliedUserIsSwitchable() throws Exception {
+ addUser(USER_ID);
+ addUser(OTHER_USER_ID);
+
+ mUms.setBootUser(OTHER_USER_ID);
+
+ assertWithMessage("getBootUser")
+ .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+ }
+
+ @Test
+ public void testSetBootUser_NotHeadless_SuppliedUserIsNotSwitchable() throws Exception {
+ setSystemUserHeadless(false);
+ addUser(USER_ID);
+ addUser(OTHER_USER_ID);
+ addDefaultProfileAndParent();
+
+ mUms.setBootUser(PROFILE_USER_ID);
+
+ assertWithMessage("getBootUser")
+ .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
+ public void testSetBootUser_Headless_SuppliedUserIsNotSwitchable() throws Exception {
+ setSystemUserHeadless(true);
+ addUser(USER_ID);
+ setLastForegroundTime(USER_ID, 1_000_000L);
+ addUser(OTHER_USER_ID);
+ setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+ addDefaultProfileAndParent();
+
+ mUms.setBootUser(PROFILE_USER_ID);
+
+ // Boot user not switchable so return most recently in foreground.
+ assertWithMessage("getBootUser")
+ .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+ }
+
+ @Test
+ public void testGetBootUser_NotHeadless_ReturnsSystemUser() throws Exception {
+ setSystemUserHeadless(false);
+ addUser(USER_ID);
+ addUser(OTHER_USER_ID);
+
+ assertWithMessage("getBootUser")
+ .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
+ public void testGetBootUser_Headless_ReturnsMostRecentlyInForeground() throws Exception {
+ setSystemUserHeadless(true);
+ addUser(USER_ID);
+ setLastForegroundTime(USER_ID, 1_000_000L);
+
+ addUser(OTHER_USER_ID);
+ setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+ assertWithMessage("getBootUser")
+ .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+ }
+
+ @Test
+ public void testGetBootUser_Headless_UserCreatedIfOnlySystemUserExists() throws Exception {
+ setSystemUserHeadless(true);
+
+ int bootUser = mUmi.getBootUser();
+
+ assertWithMessage("getStartingUser")
+ .that(bootUser).isNotEqualTo(UserHandle.USER_SYSTEM);
+
+ UserData newUser = mUsers.get(bootUser);
+ assertWithMessage("New boot user is a full user")
+ .that(newUser.info.isFull()).isTrue();
+ assertWithMessage("New boot user is an admin user")
+ .that(newUser.info.isAdmin()).isTrue();
+ assertWithMessage("New boot user is the main user")
+ .that(newUser.info.isMain()).isTrue();
+ }
+
private void mockCurrentUser(@UserIdInt int userId) {
mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
@@ -248,7 +348,7 @@
private void addUser(@UserIdInt int userId) {
TestUserData userData = new TestUserData(userId);
-
+ userData.info.flags = UserInfo.FLAG_FULL;
addUserData(userData);
}
@@ -277,6 +377,23 @@
mUsers.put(userData.info.id, userData);
}
+ private void setSystemUserHeadless(boolean headless) {
+ UserData systemUser = mUsers.get(UserHandle.USER_SYSTEM);
+ if (headless) {
+ systemUser.info.flags &= ~UserInfo.FLAG_FULL;
+ systemUser.info.userType = UserManager.USER_TYPE_SYSTEM_HEADLESS;
+ } else {
+ systemUser.info.flags |= UserInfo.FLAG_FULL;
+ systemUser.info.userType = UserManager.USER_TYPE_FULL_SYSTEM;
+ }
+ doReturn(headless).when(() -> UserManager.isHeadlessSystemUserMode());
+ }
+
+ private void setLastForegroundTime(@UserIdInt int userId, long timeMillis) {
+ UserData userData = mUsers.get(userId);
+ userData.mLastEnteredForegroundTimeMillis = timeMillis;
+ }
+
private static final class TestUserData extends UserData {
@SuppressWarnings("deprecation")
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index c08f6bf..a924207 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -306,12 +306,13 @@
verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
verifyCurrentSystemData(testUserId);
- spyOn(mService.mLastWallpaper.connection);
- doReturn(true).when(mService.mLastWallpaper.connection).isUsableDisplay(any());
+ spyOn(mService.mWallpaperDisplayHelper);
+ doReturn(true).when(mService.mWallpaperDisplayHelper)
+ .isUsableDisplay(any(Display.class), mService.mLastWallpaper.connection.mClientUid);
mService.mLastWallpaper.connection.attachEngine(mock(IWallpaperEngine.class),
DEFAULT_DISPLAY);
- WallpaperManagerService.WallpaperConnection.DisplayConnector connector =
+ WallpaperManagerService.DisplayConnector connector =
mService.mLastWallpaper.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY);
mService.setWallpaperComponent(sDefaultWallpaperComponent, FLAG_SYSTEM, testUserId);
@@ -521,7 +522,7 @@
}
private void verifyDisplayData() {
- mService.forEachDisplayData(data -> {
+ mService.mWallpaperDisplayHelper.forEachDisplayData(data -> {
assertTrue("Display width must larger than maximum screen size",
data.mWidth >= DISPLAY_SIZE_DIMENSION);
assertTrue("Display height must larger than maximum screen size",
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index e54a48b..1298e7b 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -1292,10 +1292,11 @@
unlockSystemUser();
try {
mAms.hasFeatures(
- null, // response
- AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
- new String[] {"feature1", "feature2"}, // features
- "testPackage"); // opPackageName
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ new String[] {"feature1", "feature2"}, // features
+ 0, // userId
+ "testPackage"); // opPackageName
fail("IllegalArgumentException expected. But no exception was thrown.");
} catch (IllegalArgumentException e) {
// IllegalArgumentException is expected.
@@ -1307,10 +1308,11 @@
unlockSystemUser();
try {
mAms.hasFeatures(
- mMockAccountManagerResponse, // response
- null, // account
- new String[] {"feature1", "feature2"}, // features
- "testPackage"); // opPackageName
+ mMockAccountManagerResponse, // response
+ null, // account
+ new String[] {"feature1", "feature2"}, // features
+ 0, // userId
+ "testPackage"); // opPackageName
fail("IllegalArgumentException expected. But no exception was thrown.");
} catch (IllegalArgumentException e) {
// IllegalArgumentException is expected.
@@ -1325,6 +1327,7 @@
mMockAccountManagerResponse, // response
AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
null, // features
+ 0, // userId
"testPackage"); // opPackageName
fail("IllegalArgumentException expected. But no exception was thrown.");
} catch (IllegalArgumentException e) {
@@ -1341,6 +1344,7 @@
response, // response
AccountManagerServiceTestFixtures.ACCOUNT_ERROR, // account
AccountManagerServiceTestFixtures.ACCOUNT_FEATURES, // features
+ 0, // userId
"testPackage"); // opPackageName
waitForLatch(latch);
verify(mMockAccountManagerResponse).onError(
@@ -1357,6 +1361,7 @@
response, // response
AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
AccountManagerServiceTestFixtures.ACCOUNT_FEATURES, // features
+ 0, // userId
"testPackage"); // opPackageName
waitForLatch(latch);
verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
index d4e3d44..0dd60b8 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -95,7 +96,7 @@
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
doNothing().when(mAccountManagerInternal).addOnAppPermissionChangeListener(any());
when(mJobSchedulerInternal.getSystemScheduledPendingJobs()).thenReturn(new ArrayList<>());
- mSyncManager = new SyncManagerWithMockedServices(mContext, true);
+ mSyncManager = spy(new SyncManagerWithMockedServices(mContext, true));
}
public void testSyncExtrasEquals_WithNull() throws Exception {
@@ -233,6 +234,7 @@
}
public void testShouldDisableSync() {
+ doReturn(true).when(mSyncManager).isContactSharingAllowedForCloneProfile();
UserInfo primaryUserInfo = createUserInfo("primary", 0 /* id */, 0 /* groupId */,
UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN);
UserInfo cloneUserInfo = createUserInfo("clone", 10 /* id */, 0 /* groupId */,
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 61c3f13..210aeef 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -7396,7 +7396,7 @@
verify(getServices().alarmManager, times(1)).set(anyInt(), eq(PROFILE_OFF_DEADLINE), any());
// Now the user should see a warning notification.
verify(getServices().notificationManager, times(1))
- .notify(anyInt(), any());
+ .notifyAsUser(any(), anyInt(), any(), any());
// Apps shouldn't be suspended yet.
verifyZeroInteractions(getServices().ipackageManager);
clearInvocations(getServices().alarmManager);
@@ -7410,7 +7410,7 @@
verifyZeroInteractions(getServices().alarmManager);
// Now the user should see a notification about suspended apps.
verify(getServices().notificationManager, times(1))
- .notify(anyInt(), any());
+ .notifyAsUser(any(), anyInt(), any(), any());
// Verify that the apps are suspended.
verify(getServices().ipackageManager, times(1)).setPackagesSuspendedAsUser(
any(), eq(true), any(), any(), any(), any(), anyInt());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 4ad8516..8cc362c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -280,7 +280,7 @@
}
@Test
- public void testStartRecording_notifiesCallback() {
+ public void testStartRecording_notifiesCallback_taskSession() {
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -292,6 +292,18 @@
}
@Test
+ public void testStartRecording_notifiesCallback_displaySession() {
+ // WHEN a recording is ongoing.
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ // THEN the visibility change callback is notified.
+ verify(mMediaProjectionManagerWrapper)
+ .notifyActiveProjectionCapturedContentVisibilityChanged(true);
+ }
+
+ @Test
public void testOnVisibleRequestedChanged_notifiesCallback() {
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mTaskSession);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index c373305..665d5e7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -635,6 +635,7 @@
return;
}
}
+ //TODO(b265535257): report error to either service only.
synchronized (HotwordDetectionConnection.this.mLock) {
runForEachDetectorSessionLocked((session) -> {
session.reportErrorLocked(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 19aa374..38bf9c2 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -331,6 +331,12 @@
@GuardedBy("this")
private boolean mTemporarilyDisabled;
+ /** The start value of showSessionId */
+ private static final int SHOW_SESSION_START_ID = 0;
+
+ @GuardedBy("this")
+ private int mShowSessionId = SHOW_SESSION_START_ID;
+
private final boolean mEnableService;
// TODO(b/226201975): remove reference once RoleService supports pre-created users
private final RoleObserver mRoleObserver;
@@ -350,6 +356,24 @@
}
}
+ int getNextShowSessionId() {
+ synchronized (this) {
+ // Reset the showSessionId to SHOW_SESSION_START_ID to avoid the value exceeds
+ // Integer.MAX_VALUE
+ if (mShowSessionId == Integer.MAX_VALUE - 1) {
+ mShowSessionId = SHOW_SESSION_START_ID;
+ }
+ mShowSessionId++;
+ return mShowSessionId;
+ }
+ }
+
+ int getShowSessionId() {
+ synchronized (this) {
+ return mShowSessionId;
+ }
+ }
+
@Override
public @NonNull IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
@NonNull Identity originatorIdentity, IBinder client) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 4ee3306..04c8f8f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -21,6 +21,7 @@
import static android.app.ActivityManager.START_VOICE_HIDDEN_SESSION;
import static android.app.ActivityManager.START_VOICE_NOT_ACTIVE_SESSION;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.service.voice.VoiceInteractionService.KEY_SHOW_SESSION_ID;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
@@ -255,13 +256,17 @@
/* direct= */ true);
}
- public boolean showSessionLocked(@NonNull Bundle args, int flags,
+ public boolean showSessionLocked(@Nullable Bundle args, int flags,
@Nullable String attributionTag,
@Nullable IVoiceInteractionSessionShowCallback showCallback,
@Nullable IBinder activityToken) {
+ final int sessionId = mServiceStub.getNextShowSessionId();
+ final Bundle newArgs = args == null ? new Bundle() : args;
+ newArgs.putInt(KEY_SHOW_SESSION_ID, sessionId);
+
try {
if (mService != null) {
- mService.prepareToShowSession(args, flags);
+ mService.prepareToShowSession(newArgs, flags);
}
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException while calling prepareToShowSession", e);
@@ -275,7 +280,9 @@
if (!mActiveSession.mBound) {
try {
if (mService != null) {
- mService.showSessionFailed();
+ Bundle failedArgs = new Bundle();
+ failedArgs.putInt(KEY_SHOW_SESSION_ID, sessionId);
+ mService.showSessionFailed(failedArgs);
}
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException while calling showSessionFailed", e);
@@ -300,7 +307,7 @@
} else {
visibleActivities = allVisibleActivities;
}
- return mActiveSession.showLocked(args, flags, attributionTag, mDisabledShowContext,
+ return mActiveSession.showLocked(newArgs, flags, attributionTag, mDisabledShowContext,
showCallback, visibleActivities);
}
@@ -673,6 +680,21 @@
Slog.w(TAG, "Visual query detection service not in isolated process");
throw new IllegalStateException("Visual query detection not in isolated process");
}
+ if (!Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE.equals(
+ visualQueryDetectionServiceInfo.permission)) {
+ Slog.w(TAG, "Visual query detection does not require permission "
+ + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
+ throw new SecurityException("Visual query detection does not require permission "
+ + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
+ }
+ if (mContext.getPackageManager().checkPermission(
+ Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE,
+ mInfo.getServiceInfo().packageName) == PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "Voice interaction service should not hold permission "
+ + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
+ throw new SecurityException("Voice interaction service should not hold permission "
+ + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
+ }
if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
Slog.w(TAG, "Can't set sharedMemory to be read-only");
throw new IllegalStateException("Can't set sharedMemory to be read-only");
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
new file mode 100644
index 0000000..41316d8
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.view.KeyEvent
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
+import com.android.server.wm.flicker.helpers.CameraAppHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching camera from launcher by double pressing power button
+ *
+ * To run this test: `atest FlickerTests:OpenCameraOnDoubleClickPowerButton`
+ *
+ * Actions:
+ * ```
+ * Make sure no apps are running on the device
+ * Launch an app [testApp] and wait animation to complete
+ * ```
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenCameraOnDoubleClickPowerButton(flicker: FlickerTest) :
+ OpenAppFromLauncherTransition(flicker) {
+ private val cameraApp = CameraAppHelper(instrumentation)
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ RemoveAllTasksButHomeRule.removeAllTasksButHome()
+ this.setRotation(flicker.scenario.startRotation)
+ }
+ transitions {
+ device.pressKeyCode(KeyEvent.KEYCODE_POWER)
+ device.pressKeyCode(KeyEvent.KEYCODE_POWER)
+ wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(cameraApp).waitForAndVerify()
+ }
+ teardown { RemoveAllTasksButHomeRule.removeAllTasksButHome() }
+ }
+
+ @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+
+ @Postsubmit @Test override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
+
+ @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+
+ @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+ @Postsubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+ @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
+
+ @Postsubmit
+ @Test
+ override fun appWindowReplacesLauncherAsTopWindow() =
+ super.appWindowReplacesLauncherAsTopWindow()
+
+ @Postsubmit @Test override fun focusChanges() = super.focusChanges()
+
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index f2234fb..21007ef 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -74,6 +74,7 @@
],
test_suites: ["general-tests"],
test_config: "MultiUserRollbackTest.xml",
+ data : [":RollbackTest"],
}
java_library_host {