diff options
7 files changed, 272 insertions, 0 deletions
diff --git a/core/java/android/os/storage/ICeStorageLockEventListener.java b/core/java/android/os/storage/ICeStorageLockEventListener.java new file mode 100644 index 000000000000..f16a7feb0f3a --- /dev/null +++ b/core/java/android/os/storage/ICeStorageLockEventListener.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.storage; + + +/** + * Callback class for receiving CE storage lock events from StorageManagerService. + * @hide + */ +public interface ICeStorageLockEventListener { + + /** + * Called when the CE storage corresponding to the userId is locked + */ + void onStorageLocked(int userId); +} diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java index 6995ea87ebe3..8ba2fa495540 100644 --- a/core/java/android/os/storage/StorageManagerInternal.java +++ b/core/java/android/os/storage/StorageManagerInternal.java @@ -16,10 +16,12 @@ package android.os.storage; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.UserInfo; +import android.multiuser.Flags; import android.os.IInstalld; import android.os.IVold; import android.os.ParcelFileDescriptor; @@ -201,4 +203,18 @@ public abstract class StorageManagerInternal { */ public abstract int enableFsverity(IInstalld.IFsveritySetupAuthToken authToken, String filePath, String packageName) throws IOException; + + /** + * Registers a {@link ICeStorageLockEventListener} for receiving CE storage lock events. + */ + @FlaggedApi(Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE) + public abstract void registerStorageLockEventListener( + @NonNull ICeStorageLockEventListener listener); + + /** + * Unregisters the {@link ICeStorageLockEventListener} which was registered previously + */ + @FlaggedApi(Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE) + public abstract void unregisterStorageLockEventListener( + @NonNull ICeStorageLockEventListener listener); } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index e7fae2483d16..67e18ca32e21 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -107,6 +107,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.DiskInfo; +import android.os.storage.ICeStorageLockEventListener; import android.os.storage.IObbActionListener; import android.os.storage.IStorageEventListener; import android.os.storage.IStorageManager; @@ -139,6 +140,7 @@ import android.util.TimeUtils; import android.util.Xml; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsService; import com.android.internal.content.PackageMonitor; import com.android.internal.os.AppFuseMount; @@ -185,6 +187,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -602,6 +605,9 @@ class StorageManagerService extends IStorageManager.Stub // Not guarded by lock, always used on the ActivityManager thread private final SparseArray<PackageMonitor> mPackageMonitorsForUser = new SparseArray<>(); + /** List of listeners registered for ce storage callbacks */ + private final CopyOnWriteArrayList<ICeStorageLockEventListener> + mCeStorageEventCallbacks = new CopyOnWriteArrayList<>(); class ObbState implements IBinder.DeathRecipient { public ObbState(String rawPath, String canonicalPath, int callingUid, @@ -3315,6 +3321,11 @@ class StorageManagerService extends IStorageManager.Stub synchronized (mLock) { mCeUnlockedUsers.remove(userId); } + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) { + dispatchCeStorageLockedEvent(userId); + } } @Override @@ -4580,6 +4591,18 @@ class StorageManagerService extends IStorageManager.Stub return StorageManager.MOUNT_MODE_EXTERNAL_NONE; } + @VisibleForTesting + CopyOnWriteArrayList<ICeStorageLockEventListener> getCeStorageEventCallbacks() { + return mCeStorageEventCallbacks; + } + + @VisibleForTesting + void dispatchCeStorageLockedEvent(int userId) { + for (ICeStorageLockEventListener listener: mCeStorageEventCallbacks) { + listener.onStorageLocked(userId); + } + } + private static class Callbacks extends Handler { private static final int MSG_STORAGE_STATE_CHANGED = 1; private static final int MSG_VOLUME_STATE_CHANGED = 2; @@ -5066,5 +5089,23 @@ class StorageManagerService extends IStorageManager.Stub throw new IOException(e); } } + + @Override + public void registerStorageLockEventListener( + @NonNull ICeStorageLockEventListener listener) { + boolean registered = mCeStorageEventCallbacks.add(listener); + if (!registered) { + Slog.w(TAG, "Failed to register listener: " + listener); + } + } + + @Override + public void unregisterStorageLockEventListener( + @NonNull ICeStorageLockEventListener listener) { + boolean unregistered = mCeStorageEventCallbacks.remove(listener); + if (!unregistered) { + Slog.w(TAG, "Unregistering " + listener + " that was not registered"); + } + } } } diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index baae40b88e89..5933639f2317 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -74,6 +74,15 @@ "file_patterns": ["SensitiveContentProtectionManagerService\\.java"] }, { + "name": "FrameworksMockingServicesTests", + "options": [ + { + "include-filter": "com.android.server.StorageManagerServiceTest" + } + ], + "file_patterns": ["StorageManagerService\\.java"] + }, + { "name": "FrameworksServicesTests", "options": [ { diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 593174420d94..19562ef79fbb 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -102,8 +102,10 @@ import android.os.ShellCallback; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.ICeStorageLockEventListener; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; +import android.os.storage.StorageManagerInternal; import android.provider.DeviceConfig; import android.provider.Settings; import android.security.AndroidKeyStoreMaintenance; @@ -338,6 +340,8 @@ public class LockSettingsService extends ILockSettings.Stub { private final CopyOnWriteArrayList<LockSettingsStateListener> mLockSettingsStateListeners = new CopyOnWriteArrayList<>(); + private final StorageManagerInternal mStorageManagerInternal; + // This class manages life cycle events for encrypted users on File Based Encryption (FBE) // devices. The most basic of these is to show/hide notifications about missing features until // the user unlocks the account and credential-encrypted storage is available. @@ -577,6 +581,10 @@ public class LockSettingsService extends ILockSettings.Stub { return null; } + public StorageManagerInternal getStorageManagerInternal() { + return LocalServices.getService(StorageManagerInternal.class); + } + public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) { return new SyntheticPasswordManager(getContext(), storage, getUserManager(), new PasswordSlotManager()); @@ -672,6 +680,7 @@ public class LockSettingsService extends ILockSettings.Stub { mNotificationManager = injector.getNotificationManager(); mUserManager = injector.getUserManager(); mStorageManager = injector.getStorageManager(); + mStorageManagerInternal = injector.getStorageManagerInternal(); mStrongAuthTracker = injector.getStrongAuthTracker(); mStrongAuthTracker.register(mStrongAuth); mGatekeeperPasswords = new LongSparseArray<>(); @@ -925,8 +934,34 @@ public class LockSettingsService extends ILockSettings.Stub { mStorage.prefetchUser(UserHandle.USER_SYSTEM); mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(), mInjector.getFaceManager(), mInjector.getBiometricManager()); + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) { + mStorageManagerInternal.registerStorageLockEventListener(mCeStorageLockEventListener); + } } + private final ICeStorageLockEventListener mCeStorageLockEventListener = + new ICeStorageLockEventListener() { + @Override + public void onStorageLocked(int userId) { + Slog.i(TAG, "Storage lock event received for " + userId); + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) { + mHandler.post(() -> { + UserProperties userProperties = + mUserManager.getUserProperties(UserHandle.of(userId)); + if (userProperties != null + && userProperties.getAllowStoppingUserWithDelayedLocking()) { + int strongAuthRequired = LockPatternUtils.StrongAuthTracker + .getDefaultFlags(mContext); + requireStrongAuth(strongAuthRequired, userId); + } + }); + } + }}; + private void loadEscrowData() { mRebootEscrowManager.loadRebootEscrowDataIfAvailable(mHandler); } diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS index 0eb8639bd005..dc5cb8d6bdf5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/OWNERS +++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS @@ -3,3 +3,5 @@ per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS per-file SensitiveContentProtectionManagerService* = file:/core/java/android/permission/OWNERS per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS +per-file *Storage* = file:/core/java/android/os/storage/OWNERS + diff --git a/services/tests/mockingservicestests/src/com/android/server/StorageManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/StorageManagerServiceTest.java new file mode 100644 index 000000000000..2e4b97ef7dd2 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/StorageManagerServiceTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.multiuser.Flags; +import android.os.UserManager; +import android.os.storage.ICeStorageLockEventListener; +import android.os.storage.StorageManagerInternal; +import android.platform.test.flag.junit.SetFlagsRule; + +import com.android.modules.utils.testing.ExtendedMockitoRule; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.util.concurrent.CopyOnWriteArrayList; + +public class StorageManagerServiceTest { + + private final Context mRealContext = androidx.test.platform.app.InstrumentationRegistry + .getInstrumentation().getTargetContext(); + private StorageManagerService mStorageManagerService; + private StorageManagerInternal mStorageManagerInternal; + + private static final int TEST_USER_ID = 1001; + + @Rule + public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) + .spyStatic(UserManager.class) + .build(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static class TestStorageEventListener implements ICeStorageLockEventListener { + + private int mExpectedUserId; + + private TestStorageEventListener(int userId) { + mExpectedUserId = userId; + } + + @Override + public void onStorageLocked(int userId) { + assertThat(userId).isEqualTo(mExpectedUserId); + } + } + + + @Before + public void setFixtures() { + // Called when WatchedUserStates is constructed + doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache()); + + mStorageManagerService = new StorageManagerService(mRealContext); + mStorageManagerInternal = LocalServices.getService(StorageManagerInternal.class); + assertWithMessage("LocalServices.getService(StorageManagerInternal.class)") + .that(mStorageManagerInternal).isNotNull(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(StorageManagerInternal.class); + } + + @Test + public void testRegisterLockEventListener() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE, + Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + CopyOnWriteArrayList<ICeStorageLockEventListener> storageLockEventListeners = + mStorageManagerService.getCeStorageEventCallbacks(); + assertThat(storageLockEventListeners).isNotNull(); + int registeredCallbackCount = storageLockEventListeners.size(); + TestStorageEventListener testStorageEventListener = + new TestStorageEventListener(TEST_USER_ID); + mStorageManagerInternal.registerStorageLockEventListener(testStorageEventListener); + assertNumberOfStorageCallbackReceivers(registeredCallbackCount + 1); + + mStorageManagerInternal.unregisterStorageLockEventListener(testStorageEventListener); + assertNumberOfStorageCallbackReceivers(registeredCallbackCount); + } + + @Test + public void testDispatchCeStorageLockEvent() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE, + Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); + + assertThat(mStorageManagerService.getCeStorageEventCallbacks()).isNotNull(); + int callbackReceiverSize = mStorageManagerService.getCeStorageEventCallbacks().size(); + TestStorageEventListener testStorageEventListener = + spy(new TestStorageEventListener(TEST_USER_ID)); + + // Add testStorageEventListener to the list of storage callback listeners + mStorageManagerService.getCeStorageEventCallbacks().add(testStorageEventListener); + assertNumberOfStorageCallbackReceivers(callbackReceiverSize + 1); + + mStorageManagerService.dispatchCeStorageLockedEvent(TEST_USER_ID); + verify(testStorageEventListener).onStorageLocked(eq(TEST_USER_ID)); + + // Remove testStorageEventListener from the list of storage callback listeners + mStorageManagerService.getCeStorageEventCallbacks().remove(testStorageEventListener); + assertNumberOfStorageCallbackReceivers(callbackReceiverSize); + } + + private void assertNumberOfStorageCallbackReceivers(int callbackReceiverSize) { + assertThat(mStorageManagerService.getCeStorageEventCallbacks()).isNotNull(); + assertThat(mStorageManagerService.getCeStorageEventCallbacks().size()) + .isEqualTo(callbackReceiverSize); + } +} |