diff options
34 files changed, 684 insertions, 396 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 4bddbd65c0c3..4caaa09f6a31 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -4589,6 +4589,22 @@ public class DeviceIdleController extends SystemService Binder.restoreCallingIdentity(token); } } + } else if ("force-active".equals(cmd)) { + getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, + null); + synchronized (this) { + final long token = Binder.clearCallingIdentity(); + try { + mForceIdle = true; + becomeActiveLocked("force-active", Process.myUid()); + pw.print("Light state: "); + pw.print(lightStateToString(mLightState)); + pw.print(", deep state: "); + pw.println(stateToString(mState)); + } finally { + Binder.restoreCallingIdentity(token); + } + } } else if ("force-idle".equals(cmd)) { getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); diff --git a/core/api/current.txt b/core/api/current.txt index eef16bebea83..651669c0e100 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -41246,6 +41246,7 @@ package android.service.voice { method public void setUiEnabled(boolean); method public void show(android.os.Bundle, int); method public void startAssistantActivity(android.content.Intent); + method public void startAssistantActivity(@NonNull android.content.Intent, @NonNull android.os.Bundle); method public void startVoiceActivity(android.content.Intent); method public final void unregisterVisibleActivityCallback(@NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback); field public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 6581c421f970..95e331e98ee9 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10152,7 +10152,6 @@ package android.net.wifi.sharedconnectivity.service { public abstract class SharedConnectivityService extends android.app.Service { ctor public SharedConnectivityService(); - ctor public SharedConnectivityService(@NonNull android.os.Handler); method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onConnectKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork); method public abstract void onConnectTetherNetwork(@NonNull android.net.wifi.sharedconnectivity.app.TetherNetwork); diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index f693a2f6d0cf..eeff6ccafd5d 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -66,7 +66,7 @@ import java.util.concurrent.Executor; public final class DisplayManager { private static final String TAG = "DisplayManager"; private static final boolean DEBUG = false; - private static final boolean ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE = false; + private static final boolean ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE = true; private final Context mContext; private final DisplayManagerGlobal mGlobal; diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 5778518921ca..cabcae30851f 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -26,6 +26,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.app.Activity; +import android.app.ActivityOptions; import android.app.Dialog; import android.app.DirectAction; import android.app.Instrumentation; @@ -1527,8 +1528,34 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall * <p>By default, the system will create a window for the UI for this session. If you are using * an assistant activity instead, then you can disable the window creation by calling * {@link #setUiEnabled} in {@link #onPrepareShow(Bundle, int)}.</p> + * + * NOTE: if the app would like to override some options to start the Activity, + * use {@link #startAssistantActivity(Intent, Bundle)} instead. */ public void startAssistantActivity(Intent intent) { + startAssistantActivity(intent, ActivityOptions.makeBasic().toBundle()); + } + + /** + * <p>Ask that a new assistant activity be started. This will create a new task in the + * in activity manager: this means that + * {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK} + * will be set for you to make it a new task.</p> + * + * <p>The newly started activity will be displayed on top of other activities in the system + * in a new layer that is not affected by multi-window mode. Tasks started from this activity + * will go into the normal activity layer and not this new layer.</p> + * + * <p>By default, the system will create a window for the UI for this session. If you are using + * an assistant activity instead, then you can disable the window creation by calling + * {@link #setUiEnabled} in {@link #onPrepareShow(Bundle, int)}.</p> + * + * @param intent the intent used to start an assistant activity + * @param bundle Additional options for how the Activity should be started. See + * {@link ActivityOptions} for how to build the Bundle supplied here. + */ + public void startAssistantActivity(@NonNull Intent intent, @NonNull Bundle bundle) { + Objects.requireNonNull(bundle); if (mToken == null) { throw new IllegalStateException("Can't call before onCreate()"); } @@ -1537,7 +1564,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall intent.prepareToLeaveProcess(mContext); int res = mSystemService.startAssistantActivity(mToken, intent, intent.resolveType(mContext.getContentResolver()), - mContext.getAttributionTag()); + mContext.getAttributionTag(), bundle); Instrumentation.checkStartActivityResult(res, intent); } catch (RemoteException e) { } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 5eb97862c79f..6b40d9873fbb 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -51,7 +51,7 @@ interface IVoiceInteractionManagerService { int startVoiceActivity(IBinder token, in Intent intent, String resolvedType, String attributionTag); int startAssistantActivity(IBinder token, in Intent intent, String resolvedType, - String attributionTag); + String attributionTag, in Bundle bundle); void setKeepAwake(IBinder token, boolean keepAwake); void closeSystemDialogs(IBinder token); void finish(IBinder token); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index d726b6730f30..71050fa8b233 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -506,6 +506,8 @@ applications that come with the platform <permission name="android.permission.ACCESS_BROADCAST_RADIO"/> <!-- Permission required for CTS test - CtsAmbientContextServiceTestCases --> <permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/> + <!-- Permission required for CTS test - CtsTelephonyProviderTestCases --> + <permission name="android.permission.WRITE_APN_SETTINGS"/> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java index 8f2e7b573b09..4676dffb727f 100644 --- a/media/java/android/media/tv/tuner/filter/MediaEvent.java +++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java @@ -224,6 +224,10 @@ public class MediaEvent extends FilterEvent { /** * Gets audio presentations. + * + * <p>The audio presentation order matters. As specified in ETSI EN 300 468 V1.17.1, all the + * audio programme components corresponding to the first audio preselection in the loop are + * contained in the main NGA stream. */ @NonNull public List<AudioPresentation> getAudioPresentations() { diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 0f671249596c..82ca63d1a801 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -819,6 +819,9 @@ <!-- Permission required for CTS test - ActivityCaptureCallbackTests --> <uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" /> + <!-- Permission required for CTS test - CtsTelephonyProviderTestCases --> + <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index f7126291c7f0..2b6327f3eedf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -16,7 +16,7 @@ package com.android.systemui.shade; -import static android.os.Trace.TRACE_TAG_ALWAYS; +import static android.os.Trace.TRACE_TAG_APP; import static android.view.WindowInsets.Type.systemBars; import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG; @@ -328,7 +328,7 @@ public class NotificationShadeWindowView extends FrameLayout { @Override public void requestLayout() { - Trace.instant(TRACE_TAG_ALWAYS, "NotificationShadeWindowView#requestLayout"); + Trace.instant(TRACE_TAG_APP, "NotificationShadeWindowView#requestLayout"); super.requestLayout(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 32b8e09aa730..c9f31bad74c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -166,6 +166,11 @@ public class NotifCollection implements Dumpable, PipelineDumpable { private Queue<NotifEvent> mEventQueue = new ArrayDeque<>(); + private final Runnable mRebuildListRunnable = () -> { + if (mBuildListener != null) { + mBuildListener.onBuildList(mReadOnlyNotificationSet, "asynchronousUpdate"); + } + }; private boolean mAttached = false; private boolean mAmDispatchingToOtherCode; @@ -462,7 +467,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { int modificationType) { Assert.isMainThread(); mEventQueue.add(new ChannelChangedEvent(pkgName, user, channel, modificationType)); - dispatchEventsAndRebuildList("onNotificationChannelModified"); + dispatchEventsAndAsynchronouslyRebuildList(); } private void onNotificationsInitialized() { @@ -621,15 +626,39 @@ public class NotifCollection implements Dumpable, PipelineDumpable { private void dispatchEventsAndRebuildList(String reason) { Trace.beginSection("NotifCollection.dispatchEventsAndRebuildList"); + if (mMainHandler.hasCallbacks(mRebuildListRunnable)) { + mMainHandler.removeCallbacks(mRebuildListRunnable); + } + + dispatchEvents(); + + if (mBuildListener != null) { + mBuildListener.onBuildList(mReadOnlyNotificationSet, reason); + } + Trace.endSection(); + } + + private void dispatchEventsAndAsynchronouslyRebuildList() { + Trace.beginSection("NotifCollection.dispatchEventsAndAsynchronouslyRebuildList"); + + dispatchEvents(); + + if (!mMainHandler.hasCallbacks(mRebuildListRunnable)) { + mMainHandler.postDelayed(mRebuildListRunnable, 1000L); + } + + Trace.endSection(); + } + + private void dispatchEvents() { + Trace.beginSection("NotifCollection.dispatchEvents"); + mAmDispatchingToOtherCode = true; while (!mEventQueue.isEmpty()) { mEventQueue.remove().dispatchTo(mNotifCollectionListeners); } mAmDispatchingToOtherCode = false; - if (mBuildListener != null) { - mBuildListener.onBuildList(mReadOnlyNotificationSet, reason); - } Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 1fb7eb5106e6..d2087ba6ca1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.stack; -import static android.os.Trace.TRACE_TAG_ALWAYS; +import static android.os.Trace.TRACE_TAG_APP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL; @@ -1121,7 +1121,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public void requestLayout() { - Trace.instant(TRACE_TAG_ALWAYS, "NotificationStackScrollLayout#requestLayout"); + Trace.instant(TRACE_TAG_APP, "NotificationStackScrollLayout#requestLayout"); super.requestLayout(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 005c80ab577c..540bda6ea9dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -106,6 +106,7 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; +import org.mockito.stubbing.Answer; import java.util.Arrays; import java.util.Collection; @@ -378,6 +379,90 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testScheduleBuildNotificationListWhenChannelChanged() { + // GIVEN + final NotificationEntryBuilder neb = buildNotif(TEST_PACKAGE, 48); + final NotificationChannel channel = new NotificationChannel( + "channelId", + "channelName", + NotificationManager.IMPORTANCE_DEFAULT); + neb.setChannel(channel); + + final NotifEvent notif = mNoMan.postNotif(neb); + final NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + when(mMainHandler.hasCallbacks(any())).thenReturn(false); + + clearInvocations(mBuildListener); + + // WHEN + mNotifHandler.onNotificationChannelModified(TEST_PACKAGE, + entry.getSbn().getUser(), channel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + + // THEN + verify(mMainHandler).postDelayed(any(), eq(1000L)); + } + + @Test + public void testCancelScheduledBuildNotificationListEventWhenNotifUpdatedSynchronously() { + // GIVEN + final NotificationEntry entry1 = buildNotif(TEST_PACKAGE, 1) + .setGroup(mContext, "group_1") + .build(); + final NotificationEntry entry2 = buildNotif(TEST_PACKAGE, 2) + .setGroup(mContext, "group_1") + .setContentTitle(mContext, "New version") + .build(); + final NotificationEntry entry3 = buildNotif(TEST_PACKAGE, 3) + .setGroup(mContext, "group_1") + .build(); + + final List<CoalescedEvent> entriesToBePosted = Arrays.asList( + new CoalescedEvent(entry1.getKey(), 0, entry1.getSbn(), entry1.getRanking(), null), + new CoalescedEvent(entry2.getKey(), 1, entry2.getSbn(), entry2.getRanking(), null), + new CoalescedEvent(entry3.getKey(), 2, entry3.getSbn(), entry3.getRanking(), null) + ); + + when(mMainHandler.hasCallbacks(any())).thenReturn(true); + + // WHEN + mNotifHandler.onNotificationBatchPosted(entriesToBePosted); + + // THEN + verify(mMainHandler).removeCallbacks(any()); + } + + @Test + public void testBuildNotificationListWhenChannelChanged() { + // GIVEN + final NotificationEntryBuilder neb = buildNotif(TEST_PACKAGE, 48); + final NotificationChannel channel = new NotificationChannel( + "channelId", + "channelName", + NotificationManager.IMPORTANCE_DEFAULT); + neb.setChannel(channel); + + final NotifEvent notif = mNoMan.postNotif(neb); + final NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + when(mMainHandler.hasCallbacks(any())).thenReturn(false); + when(mMainHandler.postDelayed(any(), eq(1000L))).thenAnswer((Answer) invocation -> { + final Runnable runnable = invocation.getArgument(0); + runnable.run(); + return null; + }); + + clearInvocations(mBuildListener); + + // WHEN + mNotifHandler.onNotificationChannelModified(TEST_PACKAGE, + entry.getSbn().getUser(), channel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + + // THEN + verifyBuiltList(List.of(entry)); + } + + @Test public void testRankingsAreUpdatedForOtherNotifs() { // GIVEN a collection with one notif NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java index f674a7d20b82..163f614fb65d 100644 --- a/services/companion/java/com/android/server/companion/RolesUtils.java +++ b/services/companion/java/com/android/server/companion/RolesUtils.java @@ -27,6 +27,7 @@ import android.annotation.UserIdInt; import android.app.role.RoleManager; import android.companion.AssociationInfo; import android.content.Context; +import android.os.Binder; import android.os.UserHandle; import android.util.Log; import android.util.Slog; @@ -84,7 +85,9 @@ final class RolesUtils { Slog.i(TAG, "Removing CDM role holder, role=" + deviceProfile + ", package=u" + userId + "\\" + packageName); - roleManager.removeRoleHolderAsUser(deviceProfile, packageName, + final long identity = Binder.clearCallingIdentity(); + try { + roleManager.removeRoleHolderAsUser(deviceProfile, packageName, MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(), success -> { if (!success) { @@ -92,6 +95,9 @@ final class RolesUtils { + " from the list of " + deviceProfile + " holders."); } }); + } finally { + Binder.restoreCallingIdentity(identity); + } } private RolesUtils() {}; diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index b001f3d0c892..3304fb04fa0e 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1263,6 +1263,10 @@ import java.util.concurrent.atomic.AtomicBoolean; sendILMsg(MSG_IL_BTA2DP_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs); } + /*package*/ void setLeAudioTimeout(String address, int device, int delayMs) { + sendILMsg(MSG_IL_BTLEAUDIO_TIMEOUT, SENDMSG_QUEUE, device, address, delayMs); + } + /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { synchronized (mDeviceStateLock) { mBtHelper.setAvrcpAbsoluteVolumeSupported(supported); @@ -1467,6 +1471,13 @@ import java.util.concurrent.atomic.AtomicBoolean; mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); } break; + case MSG_IL_BTLEAUDIO_TIMEOUT: + // msg.obj == address of LE Audio device + synchronized (mDeviceStateLock) { + mDeviceInventory.onMakeLeAudioDeviceUnavailableNow( + (String) msg.obj, msg.arg1); + } + break; case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; synchronized (mDeviceStateLock) { @@ -1703,12 +1714,14 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_IL_SAVE_NDEF_DEVICE_FOR_STRATEGY = 47; private static final int MSG_IL_SAVE_REMOVE_NDEF_DEVICE_FOR_STRATEGY = 48; + private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49; private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_L_SET_BT_ACTIVE_DEVICE: case MSG_IL_BTA2DP_TIMEOUT: + case MSG_IL_BTLEAUDIO_TIMEOUT: case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: case MSG_TOGGLE_HDMI: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: @@ -1800,6 +1813,7 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_L_SET_BT_ACTIVE_DEVICE: case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_IL_BTA2DP_TIMEOUT: + case MSG_IL_BTLEAUDIO_TIMEOUT: case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: if (sLastDeviceConnectMsgTime >= time) { // add a little delay to make sure messages are ordered as expected diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index f9270c9b32bb..aae1d3884344 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -395,7 +395,7 @@ public class AudioDeviceInventory { case BluetoothProfile.LE_AUDIO: case BluetoothProfile.LE_AUDIO_BROADCAST: if (switchToUnavailable) { - makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice); + makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice); } else if (switchToAvailable) { makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10, @@ -507,6 +507,12 @@ public class AudioDeviceInventory { } } + /*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device) { + synchronized (mDevicesLock) { + makeLeAudioDeviceUnavailableNow(address, device); + } + } + /*package*/ void onReportNewRoutes() { int n = mRoutesObservers.beginBroadcast(); if (n > 0) { @@ -1027,10 +1033,11 @@ public class AudioDeviceInventory { new MediaMetrics.Item(mMetricsId + "disconnectLeAudio") .record(); if (toRemove.size() > 0) { - final int delay = checkSendBecomingNoisyIntentInt(device, 0, + final int delay = checkSendBecomingNoisyIntentInt(device, + AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE); toRemove.stream().forEach(deviceAddress -> - makeLeAudioDeviceUnavailable(deviceAddress, device) + makeLeAudioDeviceUnavailableLater(deviceAddress, device, delay) ); } } @@ -1331,9 +1338,21 @@ public class AudioDeviceInventory { */ mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); - AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address, name), + final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + device, address, name), AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); + if (res != AudioSystem.AUDIO_STATUS_OK) { + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "APM failed to make available LE Audio device addr=" + address + + " error=" + res).printLog(TAG)); + // TODO: connection failed, stop here + // TODO: return; + } else { + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "LE Audio device addr=" + address + " now available").printLog(TAG)); + } + mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); @@ -1354,11 +1373,23 @@ public class AudioDeviceInventory { } @GuardedBy("mDevicesLock") - private void makeLeAudioDeviceUnavailable(String address, int device) { + private void makeLeAudioDeviceUnavailableNow(String address, int device) { if (device != AudioSystem.DEVICE_NONE) { - AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address), + final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + device, address), AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); + + if (res != AudioSystem.AUDIO_STATUS_OK) { + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "APM failed to make unavailable LE Audio device addr=" + address + + " error=" + res).printLog(TAG)); + // TODO: failed to disconnect, stop here + // TODO: return; + } else { + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "LE Audio device addr=" + address + " made unavailable").printLog(TAG)); + } mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); } @@ -1366,6 +1397,14 @@ public class AudioDeviceInventory { } @GuardedBy("mDevicesLock") + private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) { + // the device will be made unavailable later, so consider it disconnected right away + mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); + // send the delayed message to make the device unavailable later + mDeviceBroker.setLeAudioTimeout(address, device, delayMs); + } + + @GuardedBy("mDevicesLock") private void setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp) { synchronized (mCurAudioRoutes) { if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { diff --git a/services/core/java/com/android/server/pm/RestrictionsSet.java b/services/core/java/com/android/server/pm/RestrictionsSet.java index e7ad5b92ed2e..08047695a42a 100644 --- a/services/core/java/com/android/server/pm/RestrictionsSet.java +++ b/services/core/java/com/android/server/pm/RestrictionsSet.java @@ -20,7 +20,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.os.Bundle; +import android.os.UserHandle; import android.os.UserManager; +import android.util.IntArray; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; @@ -37,9 +39,7 @@ import java.util.ArrayList; import java.util.List; /** - * Data structure that contains the mapping of users to user restrictions (either the user - * restrictions that apply to them, or the user restrictions that they set, depending on the - * circumstances). + * Data structure that contains the mapping of users to user restrictions. * * @hide */ @@ -88,6 +88,24 @@ public class RestrictionsSet { } /** + * Removes a particular restriction for all users. + * + * @return whether the restriction was removed or not. + */ + public boolean removeRestrictionsForAllUsers(String restriction) { + boolean removed = false; + for (int i = 0; i < mUserRestrictions.size(); i++) { + final Bundle restrictions = mUserRestrictions.valueAt(i); + + if (UserRestrictionsUtils.contains(restrictions, restriction)) { + restrictions.remove(restriction); + removed = true; + } + } + return removed; + } + + /** * Moves a particular restriction from one restriction set to another, e.g. for all users. */ public void moveRestriction(@NonNull RestrictionsSet destRestrictions, String restriction) { @@ -139,22 +157,19 @@ public class RestrictionsSet { * @return list of enforcing users that enforce a particular restriction. */ public @NonNull List<UserManager.EnforcingUser> getEnforcingUsers(String restriction, - @UserIdInt int deviceOwnerUserId) { + @UserIdInt int userId) { final List<UserManager.EnforcingUser> result = new ArrayList<>(); - for (int i = 0; i < mUserRestrictions.size(); i++) { - if (UserRestrictionsUtils.contains(mUserRestrictions.valueAt(i), restriction)) { - result.add(getEnforcingUser(mUserRestrictions.keyAt(i), deviceOwnerUserId)); - } + if (getRestrictionsNonNull(userId).containsKey(restriction)) { + result.add(new UserManager.EnforcingUser(userId, + UserManager.RESTRICTION_SOURCE_PROFILE_OWNER)); + } + + if (getRestrictionsNonNull(UserHandle.USER_ALL).containsKey(restriction)) { + result.add(new UserManager.EnforcingUser(UserHandle.USER_ALL, + UserManager.RESTRICTION_SOURCE_DEVICE_OWNER)); } - return result; - } - private UserManager.EnforcingUser getEnforcingUser(@UserIdInt int userId, - @UserIdInt int deviceOwnerUserId) { - int source = deviceOwnerUserId == userId - ? UserManager.RESTRICTION_SOURCE_DEVICE_OWNER - : UserManager.RESTRICTION_SOURCE_PROFILE_OWNER; - return new UserManager.EnforcingUser(userId, source); + return result; } /** @@ -165,6 +180,11 @@ public class RestrictionsSet { return mUserRestrictions.get(userId); } + /** @return list of user restrictions for a given user that is not null. */ + public @NonNull Bundle getRestrictionsNonNull(@UserIdInt int userId) { + return UserRestrictionsUtils.nonNull(mUserRestrictions.get(userId)); + } + /** * Removes a given user from the restrictions set, returning true if the user has non-empty * restrictions before removal. @@ -236,6 +256,15 @@ public class RestrictionsSet { } } + /** @return list of users in this restriction set. */ + public IntArray getUserIds() { + IntArray userIds = new IntArray(mUserRestrictions.size()); + for (int i = 0; i < mUserRestrictions.size(); i++) { + userIds.add(mUserRestrictions.keyAt(i)); + } + return userIds; + } + public boolean containsKey(@UserIdInt int userId) { return mUserRestrictions.contains(userId); } diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 36efc0ddfc44..9ef1bba16351 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -141,6 +141,18 @@ public abstract class UserManagerInternal { public abstract void setDevicePolicyUserRestrictions(int originatingUserId, @Nullable Bundle global, @Nullable RestrictionsSet local, boolean isDeviceOwner); + /** + * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to set a + * user restriction. + * + * @param userId user id to apply the restriction to. {@link com.android.os.UserHandle.USER_ALL} + * will apply the restriction to all users globally. + * @param key The key of the restriction. + * @param value The value of the restriction. + */ + public abstract void setUserRestriction(@UserIdInt int userId, @NonNull String key, + boolean value); + /** Return a user restriction. */ public abstract boolean getUserRestriction(int userId, String key); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 079cc80aa987..58ae95568e3b 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -457,30 +457,12 @@ public class UserManagerService extends IUserManager.Stub { /** * User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService} - * that should be applied to all users, including guests. Only non-empty restriction bundles are - * stored. - * The key is the user id of the user whom the restriction originated from. - */ - @GuardedBy("mRestrictionsLock") - private final RestrictionsSet mDevicePolicyGlobalUserRestrictions = new RestrictionsSet(); - - /** - * Id of the user that set global restrictions. - */ - @GuardedBy("mRestrictionsLock") - private int mDeviceOwnerUserId = UserHandle.USER_NULL; - - /** - * User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService} - * for each user. + * for each user. Restrictions that apply to all users (global) are represented by + * {@link com.android.os.UserHandle.USER_ALL}. * The key is the user id of the user whom the restrictions are targeting. - * The key inside the restrictionsSet is the user id of the user whom the restriction - * originated from. - * targetUserId -> originatingUserId -> restrictionBundle */ @GuardedBy("mRestrictionsLock") - private final SparseArray<RestrictionsSet> mDevicePolicyLocalUserRestrictions = - new SparseArray<>(); + private final RestrictionsSet mDevicePolicyUserRestrictions = new RestrictionsSet(); @GuardedBy("mGuestRestrictions") private final Bundle mGuestRestrictions = new Bundle(); @@ -2567,150 +2549,69 @@ public class UserManagerService extends IUserManager.Stub { } } - /** - * See {@link UserManagerInternal#setDevicePolicyUserRestrictions} - */ - private void setDevicePolicyUserRestrictionsInner(@UserIdInt int originatingUserId, - @NonNull Bundle global, @NonNull RestrictionsSet local, - boolean isDeviceOwner) { - boolean globalChanged, localChanged; - List<Integer> updatedLocalTargetUserIds; - synchronized (mRestrictionsLock) { - // Update global and local restrictions if they were changed. - globalChanged = mDevicePolicyGlobalUserRestrictions - .updateRestrictions(originatingUserId, global); - updatedLocalTargetUserIds = getUpdatedTargetUserIdsFromLocalRestrictions( - originatingUserId, local); - localChanged = updateLocalRestrictionsForTargetUsersLR(originatingUserId, local, - updatedLocalTargetUserIds); - if (isDeviceOwner) { - // Remember the global restriction owner userId to be able to make a distinction - // in getUserRestrictionSource on who set local policies. - mDeviceOwnerUserId = originatingUserId; - } else { - if (mDeviceOwnerUserId == originatingUserId) { - // When profile owner sets restrictions it passes null global bundle and we - // reset global restriction owner userId. - // This means this user used to have DO, but now the DO is gone and the user - // instead has PO. - mDeviceOwnerUserId = UserHandle.USER_NULL; - } - } - } - if (DBG) { - Slog.d(LOG_TAG, "setDevicePolicyUserRestrictions: " - + " originatingUserId=" + originatingUserId - + " global=" + global + (globalChanged ? " (changed)" : "") - + " local=" + local + (localChanged ? " (changed)" : "") - ); - } - // Don't call them within the mRestrictionsLock. - synchronized (mPackagesLock) { - if (globalChanged || localChanged) { - if (updatedLocalTargetUserIds.size() == 1 - && updatedLocalTargetUserIds.contains(originatingUserId)) { - writeUserLP(getUserDataNoChecks(originatingUserId)); - } else { - if (globalChanged) { - writeUserLP(getUserDataNoChecks(originatingUserId)); - } - if (localChanged) { - for (int targetUserId : updatedLocalTargetUserIds) { - writeAllTargetUsersLP(targetUserId); - } - } - } - } + private void setUserRestrictionInner(int userId, @NonNull String key, boolean value) { + if (!UserRestrictionsUtils.isValidRestriction(key)) { + return; } - synchronized (mRestrictionsLock) { - if (globalChanged) { - applyUserRestrictionsForAllUsersLR(); - } else if (localChanged) { - for (int targetUserId : updatedLocalTargetUserIds) { - applyUserRestrictionsLR(targetUserId); + final Bundle newRestrictions = BundleUtils.clone( + mDevicePolicyUserRestrictions.getRestrictions(userId)); + newRestrictions.putBoolean(key, value); + + if (mDevicePolicyUserRestrictions.updateRestrictions(userId, newRestrictions)) { + if (userId == UserHandle.USER_ALL) { + applyUserRestrictionsForAllUsersLR(); + } else { + applyUserRestrictionsLR(userId); } } } } /** - * @return the list of updated target user ids in device policy local restrictions for a - * given originating user id. + * See {@link UserManagerInternal#setDevicePolicyUserRestrictions} */ - private List<Integer> getUpdatedTargetUserIdsFromLocalRestrictions(int originatingUserId, - @NonNull RestrictionsSet local) { - List<Integer> targetUserIds = new ArrayList<>(); - // Update all the target user ids from the local restrictions set - for (int i = 0; i < local.size(); i++) { - targetUserIds.add(local.keyAt(i)); - } - // Update the target user id from device policy local restrictions if the local - // restrictions set does not contain the target user id. - for (int i = 0; i < mDevicePolicyLocalUserRestrictions.size(); i++) { - int targetUserId = mDevicePolicyLocalUserRestrictions.keyAt(i); - RestrictionsSet restrictionsSet = mDevicePolicyLocalUserRestrictions.valueAt(i); - if (!local.containsKey(targetUserId) - && restrictionsSet.containsKey(originatingUserId)) { - targetUserIds.add(targetUserId); - } - } - return targetUserIds; - } + private void setDevicePolicyUserRestrictionsInner(@UserIdInt int originatingUserId, + @NonNull Bundle global, @NonNull RestrictionsSet local, + boolean isDeviceOwner) { + synchronized (mRestrictionsLock) { + final IntArray updatedUserIds = mDevicePolicyUserRestrictions.getUserIds(); - /** - * Update restrictions for all target users in the restriction set. If a target user does not - * exist in device policy local restrictions, remove the restrictions bundle for that target - * user originating from the specified originating user. - */ - @GuardedBy("mRestrictionsLock") - private boolean updateLocalRestrictionsForTargetUsersLR(int originatingUserId, - RestrictionsSet local, List<Integer> updatedTargetUserIds) { - boolean changed = false; - for (int targetUserId : updatedTargetUserIds) { - Bundle restrictions = local.getRestrictions(targetUserId); - if (restrictions == null) { - restrictions = new Bundle(); - } - if (getDevicePolicyLocalRestrictionsForTargetUserLR(targetUserId) - .updateRestrictions(originatingUserId, restrictions)) { - changed = true; + mCachedEffectiveUserRestrictions.removeAllRestrictions(); + mDevicePolicyUserRestrictions.removeAllRestrictions(); + + mDevicePolicyUserRestrictions.updateRestrictions(UserHandle.USER_ALL, global); + + final IntArray localUserIds = local.getUserIds(); + for (int i = 0; i < localUserIds.size(); i++) { + final int userId = localUserIds.get(i); + mDevicePolicyUserRestrictions.updateRestrictions(userId, + local.getRestrictions(userId)); + updatedUserIds.add(userId); } - } - return changed; - } - /** - * A new restriction set is created if a restriction set does not already exist for a given - * target user. - * - * @return restrictions set for a given target user. - */ - @GuardedBy("mRestrictionsLock") - private @NonNull RestrictionsSet getDevicePolicyLocalRestrictionsForTargetUserLR( - int targetUserId) { - RestrictionsSet result = mDevicePolicyLocalUserRestrictions.get(targetUserId); - if (result == null) { - result = new RestrictionsSet(); - mDevicePolicyLocalUserRestrictions.put(targetUserId, result); + applyUserRestrictionsForAllUsersLR(); + for (int i = 0; i < updatedUserIds.size(); i++) { + applyUserRestrictionsLR(updatedUserIds.get(i)); + } } - return result; } @GuardedBy("mRestrictionsLock") private Bundle computeEffectiveUserRestrictionsLR(@UserIdInt int userId) { - final Bundle baseRestrictions = - UserRestrictionsUtils.nonNull(mBaseUserRestrictions.getRestrictions(userId)); - final Bundle global = mDevicePolicyGlobalUserRestrictions.mergeAll(); - final RestrictionsSet local = getDevicePolicyLocalRestrictionsForTargetUserLR(userId); + final Bundle baseRestrictions = mBaseUserRestrictions.getRestrictionsNonNull(userId); + + final Bundle global = mDevicePolicyUserRestrictions.getRestrictionsNonNull( + UserHandle.USER_ALL); + final Bundle local = mDevicePolicyUserRestrictions.getRestrictionsNonNull(userId); - if (BundleUtils.isEmpty(global) && local.isEmpty()) { + if (global.isEmpty() && local.isEmpty()) { // Common case first. return baseRestrictions; } final Bundle effective = BundleUtils.clone(baseRestrictions); UserRestrictionsUtils.merge(effective, global); - UserRestrictionsUtils.merge(effective, local.mergeAll()); + UserRestrictionsUtils.merge(effective, local); return effective; } @@ -2834,13 +2735,7 @@ public class UserManagerService extends IUserManager.Stub { } synchronized (mRestrictionsLock) { - // Check if it is set as a local restriction. - result.addAll(getDevicePolicyLocalRestrictionsForTargetUserLR(userId).getEnforcingUsers( - restrictionKey, mDeviceOwnerUserId)); - - // Check if it is set as a global restriction. - result.addAll(mDevicePolicyGlobalUserRestrictions.getEnforcingUsers(restrictionKey, - mDeviceOwnerUserId)); + result.addAll(mDevicePolicyUserRestrictions.getEnforcingUsers(restrictionKey, userId)); } return result; } @@ -2990,6 +2885,7 @@ public class UserManagerService extends IUserManager.Stub { @GuardedBy("mRestrictionsLock") private void applyUserRestrictionsLR(@UserIdInt int userId) { updateUserRestrictionsInternalLR(null, userId); + scheduleWriteUser(getUserDataNoChecks(userId)); } @GuardedBy("mRestrictionsLock") @@ -3666,10 +3562,6 @@ public class UserManagerService extends IUserManager.Stub { parser.getAttributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion); } - // Pre-O global user restriction were stored as a single bundle (as opposed to per-user - // currently), take care of it in case of upgrade. - Bundle oldDevicePolicyGlobalUserRestrictions = null; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.START_TAG) { final String name = parser.getName(); @@ -3698,23 +3590,12 @@ public class UserManagerService extends IUserManager.Stub { break; } } - } else if (name.equals(TAG_DEVICE_OWNER_USER_ID) - // Legacy name, should only be encountered when upgrading from pre-O. - || name.equals(TAG_GLOBAL_RESTRICTION_OWNER_ID)) { - synchronized (mRestrictionsLock) { - mDeviceOwnerUserId = - parser.getAttributeInt(null, ATTR_ID, mDeviceOwnerUserId); - } - } else if (name.equals(TAG_DEVICE_POLICY_RESTRICTIONS)) { - // Should only happen when upgrading from pre-O (version < 7). - oldDevicePolicyGlobalUserRestrictions = - UserRestrictionsUtils.readRestrictions(parser); } } } updateUserIds(); - upgradeIfNecessaryLP(oldDevicePolicyGlobalUserRestrictions); + upgradeIfNecessaryLP(); } catch (IOException | XmlPullParserException e) { fallbackToSingleUserLP(); } finally { @@ -3724,21 +3605,19 @@ public class UserManagerService extends IUserManager.Stub { /** * Upgrade steps between versions, either for fixing bugs or changing the data format. - * @param oldGlobalUserRestrictions Pre-O global device policy restrictions. */ @GuardedBy({"mPackagesLock"}) - private void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions) { - upgradeIfNecessaryLP(oldGlobalUserRestrictions, mUserVersion, mUserTypeVersion); + private void upgradeIfNecessaryLP() { + upgradeIfNecessaryLP(mUserVersion, mUserTypeVersion); } /** - * Version of {@link #upgradeIfNecessaryLP(Bundle)} that takes in the userVersion for testing - * purposes. For non-tests, use {@link #upgradeIfNecessaryLP(Bundle)}. + * Version of {@link #upgradeIfNecessaryLP()} that takes in the userVersion for testing + * purposes. For non-tests, use {@link #upgradeIfNecessaryLP()}. */ @GuardedBy({"mPackagesLock"}) @VisibleForTesting - void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions, int userVersion, - int userTypeVersion) { + void upgradeIfNecessaryLP(int userVersion, int userTypeVersion) { Slog.i(LOG_TAG, "Upgrading users from userVersion " + userVersion + " to " + USER_VERSION); Set<Integer> userIdsToWrite = new ArraySet<>(); final int originalVersion = mUserVersion; @@ -3792,16 +3671,11 @@ public class UserManagerService extends IUserManager.Stub { if (userVersion < 7) { // Previously only one user could enforce global restrictions, now it is per-user. synchronized (mRestrictionsLock) { - if (!BundleUtils.isEmpty(oldGlobalUserRestrictions) - && mDeviceOwnerUserId != UserHandle.USER_NULL) { - mDevicePolicyGlobalUserRestrictions.updateRestrictions( - mDeviceOwnerUserId, oldGlobalUserRestrictions); + if (mDevicePolicyUserRestrictions.removeRestrictionsForAllUsers( + UserManager.ENSURE_VERIFY_APPS)) { + mDevicePolicyUserRestrictions.getRestrictionsNonNull(UserHandle.USER_ALL) + .putBoolean(UserManager.ENSURE_VERIFY_APPS, true); } - // ENSURE_VERIFY_APPS is now enforced globally even if put by profile owner, so move - // it from local to global bundle for all users who set it. - UserRestrictionsUtils.moveRestriction(UserManager.ENSURE_VERIFY_APPS, - mDevicePolicyLocalUserRestrictions, mDevicePolicyGlobalUserRestrictions - ); } // DISALLOW_CONFIG_WIFI was made a default guest restriction some time during version 6. final List<UserInfo> guestUsers = getGuestUsers(); @@ -4135,17 +4009,6 @@ public class UserManagerService extends IUserManager.Stub { } @GuardedBy({"mPackagesLock"}) - private void writeAllTargetUsersLP(int originatingUserId) { - for (int i = 0; i < mDevicePolicyLocalUserRestrictions.size(); i++) { - int targetUserId = mDevicePolicyLocalUserRestrictions.keyAt(i); - RestrictionsSet restrictionsSet = mDevicePolicyLocalUserRestrictions.valueAt(i); - if (restrictionsSet.containsKey(originatingUserId)) { - writeUserLP(getUserDataNoChecks(targetUserId)); - } - } - } - - @GuardedBy({"mPackagesLock"}) private void writeUserLP(UserData userData) { if (DBG) { debug("writeUserLP " + userData); @@ -4231,11 +4094,14 @@ public class UserManagerService extends IUserManager.Stub { synchronized (mRestrictionsLock) { UserRestrictionsUtils.writeRestrictions(serializer, mBaseUserRestrictions.getRestrictions(userInfo.id), TAG_RESTRICTIONS); - getDevicePolicyLocalRestrictionsForTargetUserLR(userInfo.id).writeRestrictions( - serializer, TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS); + + UserRestrictionsUtils.writeRestrictions(serializer, + mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL), + TAG_DEVICE_POLICY_RESTRICTIONS); + UserRestrictionsUtils.writeRestrictions(serializer, - mDevicePolicyGlobalUserRestrictions.getRestrictions(userInfo.id), - TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS); + mDevicePolicyUserRestrictions.getRestrictions(userInfo.id), + TAG_DEVICE_POLICY_RESTRICTIONS); } if (userData.account != null) { @@ -4303,11 +4169,6 @@ public class UserManagerService extends IUserManager.Stub { .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS); } serializer.endTag(null, TAG_GUEST_RESTRICTIONS); - serializer.startTag(null, TAG_DEVICE_OWNER_USER_ID); - synchronized (mRestrictionsLock) { - serializer.attributeInt(null, ATTR_ID, mDeviceOwnerUserId); - } - serializer.endTag(null, TAG_DEVICE_OWNER_USER_ID); int[] userIdsToWrite; synchronized (mUsersLock) { userIdsToWrite = new int[mUsers.size()]; @@ -4379,7 +4240,7 @@ public class UserManagerService extends IUserManager.Stub { UserProperties userProperties = null; Bundle baseRestrictions = null; Bundle legacyLocalRestrictions = null; - RestrictionsSet localRestrictions = null; + Bundle localRestrictions = null; Bundle globalRestrictions = null; boolean ignorePrepareStorageErrors = true; // default is true for old users @@ -4445,8 +4306,7 @@ public class UserManagerService extends IUserManager.Stub { } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) { legacyLocalRestrictions = UserRestrictionsUtils.readRestrictions(parser); } else if (TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS.equals(tag)) { - localRestrictions = RestrictionsSet.readRestrictions(parser, - TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS); + localRestrictions = UserRestrictionsUtils.readRestrictions(parser); } else if (TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS.equals(tag)) { globalRestrictions = UserRestrictionsUtils.readRestrictions(parser); } else if (TAG_ACCOUNT.equals(tag)) { @@ -4516,19 +4376,15 @@ public class UserManagerService extends IUserManager.Stub { mBaseUserRestrictions.updateRestrictions(id, baseRestrictions); } if (localRestrictions != null) { - mDevicePolicyLocalUserRestrictions.put(id, localRestrictions); + mDevicePolicyUserRestrictions.updateRestrictions(id, localRestrictions); if (legacyLocalRestrictions != null) { Slog.wtf(LOG_TAG, "Seeing both legacy and current local restrictions in xml"); } } else if (legacyLocalRestrictions != null) { - RestrictionsSet legacyLocalRestrictionsSet = - legacyLocalRestrictions.isEmpty() - ? new RestrictionsSet() - : new RestrictionsSet(id, legacyLocalRestrictions); - mDevicePolicyLocalUserRestrictions.put(id, legacyLocalRestrictionsSet); + mDevicePolicyUserRestrictions.updateRestrictions(id, legacyLocalRestrictions); } if (globalRestrictions != null) { - mDevicePolicyGlobalUserRestrictions.updateRestrictions(id, + mDevicePolicyUserRestrictions.updateRestrictions(UserHandle.USER_ALL, globalRestrictions); } } @@ -5364,18 +5220,12 @@ public class UserManagerService extends IUserManager.Stub { } } else if (atomTag == FrameworkStatsLog.MULTI_USER_INFO) { if (UserManager.getMaxSupportedUsers() > 1) { - int deviceOwnerUserId = UserHandle.USER_NULL; - - synchronized (mRestrictionsLock) { - deviceOwnerUserId = mDeviceOwnerUserId; - } - data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.MULTI_USER_INFO, UserManager.getMaxSupportedUsers(), - isUserSwitcherEnabled(deviceOwnerUserId), + isUserSwitcherEnabled(UserHandle.USER_ALL), UserManager.supportsMultipleUsers() && !hasUserRestriction(UserManager.DISALLOW_ADD_USER, - deviceOwnerUserId))); + UserHandle.USER_ALL))); } } else { Slogf.e(LOG_TAG, "Unexpected atom tag: %d", atomTag); @@ -5843,17 +5693,8 @@ public class UserManagerService extends IUserManager.Stub { mBaseUserRestrictions.remove(userId); mAppliedUserRestrictions.remove(userId); mCachedEffectiveUserRestrictions.remove(userId); - // Remove local restrictions affecting user - mDevicePolicyLocalUserRestrictions.delete(userId); - // Remove local restrictions set by user - boolean changed = false; - for (int i = 0; i < mDevicePolicyLocalUserRestrictions.size(); i++) { - int targetUserId = mDevicePolicyLocalUserRestrictions.keyAt(i); - changed |= getDevicePolicyLocalRestrictionsForTargetUserLR(targetUserId) - .remove(userId); - } - changed |= mDevicePolicyGlobalUserRestrictions.remove(userId); - if (changed) { + // Remove restrictions affecting the user + if (mDevicePolicyUserRestrictions.remove(userId)) { applyUserRestrictionsForAllUsersLR(); } } @@ -6587,10 +6428,12 @@ public class UserManagerService extends IUserManager.Stub { pw.println(); pw.println("Device properties:"); + pw.println(" Device policy global restrictions:"); synchronized (mRestrictionsLock) { - pw.println(" Device owner id:" + mDeviceOwnerUserId); + UserRestrictionsUtils.dumpRestrictions( + pw, " ", + mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL)); } - pw.println(); pw.println(" Guest restrictions:"); synchronized (mGuestRestrictions) { UserRestrictionsUtils.dumpRestrictions(pw, " ", mGuestRestrictions); @@ -6768,13 +6611,10 @@ public class UserManagerService extends IUserManager.Stub { synchronized (mRestrictionsLock) { UserRestrictionsUtils.dumpRestrictions( pw, " ", mBaseUserRestrictions.getRestrictions(userInfo.id)); - pw.println(" Device policy global restrictions:"); + pw.println(" Device policy restrictions:"); UserRestrictionsUtils.dumpRestrictions( pw, " ", - mDevicePolicyGlobalUserRestrictions.getRestrictions(userInfo.id)); - pw.println(" Device policy local restrictions:"); - getDevicePolicyLocalRestrictionsForTargetUserLR( - userInfo.id).dumpRestrictions(pw, " "); + mDevicePolicyUserRestrictions.getRestrictions(userInfo.id)); pw.println(" Effective restrictions:"); UserRestrictionsUtils.dumpRestrictions( pw, " ", @@ -6857,6 +6697,11 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public void setUserRestriction(int userId, @NonNull String key, boolean value) { + UserManagerService.this.setUserRestrictionInner(userId, key, value); + } + + @Override public boolean getUserRestriction(@UserIdInt int userId, String key) { return getUserRestrictions(userId).getBoolean(key); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java index e16b0f7c3f17..c42a4573d2e3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java @@ -71,6 +71,10 @@ import java.util.function.Predicate; import java.util.stream.Collectors; class ActiveAdmin { + + private final int userId; + public final boolean isPermissionBased; + private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features"; private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin"; private static final String TAG_DISABLE_CAMERA = "disable-camera"; @@ -356,8 +360,20 @@ class ActiveAdmin { String mSmsPackage; ActiveAdmin(DeviceAdminInfo info, boolean isParent) { + this.userId = -1; this.info = info; this.isParent = isParent; + this.isPermissionBased = false; + } + + ActiveAdmin(int userId, boolean permissionBased) { + if (permissionBased == false) { + throw new IllegalArgumentException("Can only pass true for permissionBased admin"); + } + this.userId = userId; + this.isPermissionBased = permissionBased; + this.isParent = false; + this.info = null; } ActiveAdmin getParentActiveAdmin() { @@ -374,10 +390,16 @@ class ActiveAdmin { } int getUid() { + if (isPermissionBased) { + return -1; + } return info.getActivityInfo().applicationInfo.uid; } public UserHandle getUserHandle() { + if (isPermissionBased) { + return UserHandle.of(userId); + } return UserHandle.of(UserHandle.getUserId(info.getActivityInfo().applicationInfo.uid)); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java index a5b9d4397eaf..6d51bd7c7770 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java @@ -133,9 +133,9 @@ class DevicePolicyData { // Create or get the permission-based admin. The permission-based admin will not have a // DeviceAdminInfo or ComponentName. - ActiveAdmin createOrGetPermissionBasedAdmin() { + ActiveAdmin createOrGetPermissionBasedAdmin(int userId) { if (mPermissionBasedAdmin == null) { - mPermissionBasedAdmin = new ActiveAdmin(/* info= */ null, /* parent= */ false); + mPermissionBasedAdmin = new ActiveAdmin(userId, /* permissionBased= */ true); } return mPermissionBasedAdmin; } @@ -509,7 +509,7 @@ class DevicePolicyData { Slogf.w(TAG, e, "Failed loading admin %s", name); } } else if ("permission-based-admin".equals(tag)) { - ActiveAdmin ap = new ActiveAdmin(/* info= */ null, /* parent= */ false); + ActiveAdmin ap = new ActiveAdmin(policy.mUserId, /* permissionBased= */ true); ap.readFromXml(parser, /* overwritePolicies= */ false); policy.mPermissionBasedAdmin = ap; } else if ("delegation".equals(tag)) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 821a5f667b7b..5e71a55aec79 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3616,7 +3616,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int N = admins.size(); for (int i = 0; i < N; i++) { ActiveAdmin admin = admins.get(i); - if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD) + if ((admin.isPermissionBased || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) && admin.passwordExpirationTimeout > 0L && now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS && admin.passwordExpirationDate > 0L) { @@ -4296,15 +4296,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @GuardedBy("getLockObject()") private List<ActiveAdmin> getActiveAdminsForLockscreenPoliciesLocked(int userHandle) { if (isSeparateProfileChallengeEnabled(userHandle)) { + + if (isPermissionCheckFlagEnabled()) { + return getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(userHandle); + } // If this user has a separate challenge, only return its restrictions. return getUserDataUnchecked(userHandle).mAdminList; } // If isSeparateProfileChallengeEnabled is false and userHandle points to a managed profile // we need to query the parent user who owns the credential. - return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle), - (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); + if (isPermissionCheckFlagEnabled()) { + return getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(getProfileParentId(userHandle), + (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); + } else { + return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle), + (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); + } + } /** @@ -4340,7 +4351,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @GuardedBy("getLockObject()") private List<ActiveAdmin> getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked( int userHandle) { - List<ActiveAdmin> list = getActiveAdminsForAffectedUserLocked(userHandle); + List<ActiveAdmin> list; + + if (isManagedProfile(userHandle)) { + list = getUserDataUnchecked(userHandle).mAdminList; + } + list = getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(userHandle, + /* shouldIncludeProfileAdmins */ (user) -> false); + if (getUserData(userHandle).mPermissionBasedAdmin != null) { list.add(getUserData(userHandle).mPermissionBasedAdmin); } @@ -4378,6 +4396,44 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return admins; } + /** + * Returns the list of admins on the given user, as well as parent admins for each managed + * profile associated with the given user. Optionally also include the admin of each managed + * profile. + * <p> Should not be called on a profile user. + */ + @GuardedBy("getLockObject()") + private List<ActiveAdmin> getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(int userHandle, + Predicate<UserInfo> shouldIncludeProfileAdmins) { + ArrayList<ActiveAdmin> admins = new ArrayList<>(); + mInjector.binderWithCleanCallingIdentity(() -> { + for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) { + DevicePolicyData policy = getUserDataUnchecked(userInfo.id); + if (userInfo.id == userHandle) { + admins.addAll(policy.mAdminList); + if (policy.mPermissionBasedAdmin != null) { + admins.add(policy.mPermissionBasedAdmin); + } + } else if (userInfo.isManagedProfile()) { + for (int i = 0; i < policy.mAdminList.size(); i++) { + ActiveAdmin admin = policy.mAdminList.get(i); + if (admin.hasParentActiveAdmin()) { + admins.add(admin.getParentActiveAdmin()); + } + if (shouldIncludeProfileAdmins.test(userInfo)) { + admins.add(admin); + } + } + if (policy.mPermissionBasedAdmin != null + && shouldIncludeProfileAdmins.test(userInfo)) { + admins.add(policy.mPermissionBasedAdmin); + } + } + } + }); + return admins; + } + private boolean isSeparateProfileChallengeEnabled(int userHandle) { return mInjector.binderWithCleanCallingIdentity(() -> mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)); @@ -4697,6 +4753,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * Return a single admin's expiration date/time, or the min (soonest) for all admins. * Returns 0 if not configured. */ + @GuardedBy("getLockObject()") private long getPasswordExpirationLocked(ComponentName who, int userHandle, boolean parent) { long timeout = 0L; @@ -5282,11 +5339,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { adminPackageName, userId, affectedUserId, complexity); } } - + @GuardedBy("getLockObject()") private int getAggregatedPasswordComplexityLocked(@UserIdInt int userHandle) { return getAggregatedPasswordComplexityLocked(userHandle, false); } + @GuardedBy("getLockObject()") private int getAggregatedPasswordComplexityLocked(@UserIdInt int userHandle, boolean deviceWideOnly) { ensureLocked(); @@ -5469,6 +5527,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * profile. * Returns {@code null} if no participating admin has that policy set. */ + @GuardedBy("getLockObject()") private ActiveAdmin getAdminWithMinimumFailedPasswordsForWipeLocked( int userHandle, boolean parent) { int count = 0; @@ -5699,6 +5758,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @GuardedBy("getLockObject()") private void updateMaximumTimeToLockLocked(@UserIdInt int userId) { // Update the profile's timeout if (isManagedProfile(userId)) { @@ -5727,6 +5787,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } + @GuardedBy("getLockObject()") private void updateProfileLockTimeoutLocked(@UserIdInt int userId) { final long timeMs; if (isSeparateProfileChallengeEnabled(userId)) { @@ -7459,9 +7520,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final String adminName; final ComponentName adminComp; if (admin != null) { - adminComp = admin.info.getComponent(); - adminName = adminComp.flattenToShortString(); - event.setAdmin(adminComp); + if (admin.isPermissionBased) { + adminComp = null; + adminName = caller.getPackageName(); + event.setAdmin(adminName); + } else { + adminComp = admin.info.getComponent(); + adminName = adminComp.flattenToShortString(); + event.setAdmin(adminComp); + } } else { adminComp = null; adminName = mInjector.getPackageManager().getPackagesForUid(caller.getUid())[0]; @@ -7750,13 +7817,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || hasCallingPermission(permission.MASTER_CLEAR) || hasCallingPermission(MANAGE_DEVICE_POLICY_FACTORY_RESET), "Must be called by the FRP management agent on device"); - // TODO(b/261999445): Remove - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.getUserId(frpManagementAgentUid)); - } + admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked(); } else { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) @@ -7927,12 +7988,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * * @return the set of user IDs that have been affected */ + @GuardedBy("getLockObject()") private Set<Integer> updatePasswordExpirationsLocked(int userHandle) { final ArraySet<Integer> affectedUserIds = new ArraySet<>(); List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(userHandle); for (int i = 0; i < admins.size(); i++) { ActiveAdmin admin = admins.get(i); - if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) { + if (admin.isPermissionBased || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) { affectedUserIds.add(admin.getUserHandle().getIdentifier()); long timeout = admin.passwordExpirationTimeout; admin.passwordExpirationDate = @@ -8026,6 +8088,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { */ private int getUserIdToWipeForFailedPasswords(ActiveAdmin admin) { final int userId = admin.getUserHandle().getIdentifier(); + if (admin.isPermissionBased) { + return userId; + } final ComponentName component = admin.info.getComponent(); return isProfileOwnerOfOrganizationOwnedDevice(component, userId) ? getProfileParentId(userId) : userId; @@ -9653,6 +9718,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return admin; } + ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked() { + ensureLocked(); + ActiveAdmin doOrPo = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); + if (isPermissionCheckFlagEnabled() && doOrPo == null) { + return getUserData(0).mPermissionBasedAdmin; + } + return doOrPo; + } + ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceParentLocked(int userId) { ensureLocked(); ActiveAdmin admin = getDeviceOwnerAdminLocked(); @@ -10659,9 +10733,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } - final ComponentName profileOwner = getProfileOwnerAsUser(userId); - if (profileOwner == null) { - return false; + if (!isPermissionCheckFlagEnabled()) { + // TODO: Figure out if something like this needs to be restored for policy engine + final ComponentName profileOwner = getProfileOwnerAsUser(userId); + if (profileOwner == null) { + return false; + } } // Managed profiles are not allowed to use lock task @@ -11884,7 +11961,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { List<String> result = null; // Only device or profile owners can have permitted lists set. - List<ActiveAdmin> admins = getActiveAdminsForAffectedUserLocked(userId); + List<ActiveAdmin> admins = getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(userId); for (ActiveAdmin admin: admins) { List<String> fromAdmin = admin.permittedInputMethods; if (fromAdmin != null) { @@ -12597,7 +12674,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } return policies.get(enforcingAdmin).getValue(); } else { - Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, @@ -13623,7 +13699,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { new BooleanPolicyValue(uninstallBlocked), caller.getUserId()); } else { - Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller))) @@ -14265,7 +14340,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int userId = mInjector.userHandleGetCallingUserId(); // Is it ok to just check that no active policies exist currently? - if (mDevicePolicyEngine.hasActivePolicies()) { + if (isDevicePolicyEngineFlagEnabled() && mDevicePolicyEngine.hasActivePolicies()) { LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy( PolicyDefinition.LOCK_TASK, userId); if (policy == null) { @@ -15814,7 +15889,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (admin.mPasswordPolicy.quality < minPasswordQuality) { return false; } - return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + return admin.isPermissionBased || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); } @Override @@ -21451,13 +21526,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } synchronized (getLockObject()) { ActiveAdmin admin; - // TODO(b/261999445): remove - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked(); return admin != null ? admin.mWifiSsidPolicy : null; } } @@ -22454,7 +22523,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin); } if (admin == null) { - admin = getUserData(userId).createOrGetPermissionBasedAdmin(); + admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId); } return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin); } @@ -23014,26 +23083,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return admins; } - // TODO: This can actually accept an EnforcingAdmin that gets created in the permission check - // method. private boolean useDevicePolicyEngine(CallerIdentity caller, @Nullable String delegateScope) { - if (!isCallerActiveAdminOrDelegate(caller, delegateScope)) { - if (!isDevicePolicyEngineFlagEnabled()) { - throw new IllegalStateException("Non DPC caller can't set device policies."); - } - if (hasDPCsNotSupportingCoexistence()) { - throw new IllegalStateException("Non DPC caller can't set device policies with " - + "existing legacy admins on the device."); - } - return true; - } else { - return isDevicePolicyEngineEnabled(); - } + return isDevicePolicyEngineEnabled(); } private boolean isDevicePolicyEngineEnabled() { - return isDevicePolicyEngineFlagEnabled() && !hasDPCsNotSupportingCoexistence() - && isPermissionCheckFlagEnabled(); + return isDevicePolicyEngineFlagEnabled() && isPermissionCheckFlagEnabled(); } private boolean isDevicePolicyEngineFlagEnabled() { diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/RestrictionsSetTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/RestrictionsSetTest.java index e7adf7b757f1..8345a43979af 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/RestrictionsSetTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/RestrictionsSetTest.java @@ -32,6 +32,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.runner.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -138,6 +139,7 @@ public class RestrictionsSetTest { } @Test + @Ignore("b/268334580") public void testGetEnforcingUsers_hasEnforcingUser() { mRestrictionsSet.updateRestrictions(originatingUserId, newRestrictions(UserManager.ENSURE_VERIFY_APPS)); @@ -154,6 +156,7 @@ public class RestrictionsSetTest { } @Test + @Ignore("b/268334580") public void testGetEnforcingUsers_hasMultipleEnforcingUsers() { int originatingUserId2 = 10; mRestrictionsSet.updateRestrictions(originatingUserId, diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java index d999aa315940..2273fcd22b38 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java @@ -230,7 +230,7 @@ public class UserManagerServiceUserInfoTest { mUserManagerService.putUserInfo(createUser(105, FLAG_SYSTEM | FLAG_FULL, null)); mUserManagerService.putUserInfo(createUser(106, FLAG_DEMO | FLAG_FULL, null)); - mUserManagerService.upgradeIfNecessaryLP(null, versionToTest - 1, userTypeVersion); + mUserManagerService.upgradeIfNecessaryLP(versionToTest - 1, userTypeVersion); assertTrue(mUserManagerService.isUserOfType(100, USER_TYPE_PROFILE_MANAGED)); assertTrue((mUserManagerService.getUserInfo(100).flags & FLAG_PROFILE) != 0); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index d4374a9c2654..fd542939373b 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -1049,7 +1049,8 @@ public class VoiceInteractionManagerService extends SystemService { @Override public int startAssistantActivity(@NonNull IBinder token, @NonNull Intent intent, - @Nullable String resolvedType, @Nullable String attributionTag) { + @Nullable String resolvedType, @NonNull String attributionTag, + @NonNull Bundle bundle) { synchronized (this) { if (mImpl == null) { Slog.w(TAG, "startAssistantActivity without running voice interaction service"); @@ -1060,7 +1061,7 @@ public class VoiceInteractionManagerService extends SystemService { final long caller = Binder.clearCallingIdentity(); try { return mImpl.startAssistantActivityLocked(attributionTag, callingPid, - callingUid, token, intent, resolvedType); + callingUid, token, intent, resolvedType, bundle); } finally { Binder.restoreCallingIdentity(caller); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index ad0e9217c50b..96b69f8c4130 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -29,7 +29,6 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; -import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.AppGlobals; import android.app.ApplicationExitInfo; @@ -376,7 +375,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne @GuardedBy("this") public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid, - int callingUid, IBinder token, Intent intent, String resolvedType) { + int callingUid, IBinder token, Intent intent, String resolvedType, + @NonNull Bundle bundle) { try { if (mActiveSession == null || token != mActiveSession.mToken) { Slog.w(TAG, "startAssistantActivity does not match active session"); @@ -388,10 +388,10 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } intent = new Intent(intent); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchActivityType(ACTIVITY_TYPE_ASSISTANT); + // TODO: make the key public hidden + bundle.putInt("android.activity.activityType", ACTIVITY_TYPE_ASSISTANT); return mAtm.startAssistantActivity(mComponent.getPackageName(), callingFeatureId, - callingPid, callingUid, intent, resolvedType, options.toBundle(), mUser); + callingPid, callingUid, intent, resolvedType, bundle, mUser); } catch (RemoteException e) { throw new IllegalStateException("Unexpected remote error", e); } diff --git a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl index d6f8012201ab..abf2b55de76a 100644 --- a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl +++ b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl @@ -16,6 +16,7 @@ package android.telephony.satellite; +import android.telephony.satellite.ISatelliteDatagramReceiverAck; import android.telephony.satellite.SatelliteDatagram; /** @@ -24,8 +25,15 @@ import android.telephony.satellite.SatelliteDatagram; */ oneway interface ISatelliteDatagramCallback { /** - * Called when there are incoming datagrams to be received. - * @param datagrams Array of datagrams to be received over satellite. + * Called when datagrams are received from satellite. + * + * @param datagramId An id that uniquely identifies incoming datagram. + * @param datagram datagram received from satellite. + * @param pendingCount Number of datagrams yet to be received from satellite. + * @param callback This callback will be used by datagram receiver app to send ack back to + * Telephony. If the callback is not received within five minutes, + * Telephony will resend the datagrams. */ - void onSatelliteDatagrams(in SatelliteDatagram[] datagrams); + void onSatelliteDatagramReceived(long datagramId, in SatelliteDatagram datagram, + int pendingCount, ISatelliteDatagramReceiverAck callback); } diff --git a/telephony/java/android/telephony/satellite/ISatelliteDatagramReceiverAck.aidl b/telephony/java/android/telephony/satellite/ISatelliteDatagramReceiverAck.aidl new file mode 100644 index 000000000000..eeb0ac56d6bf --- /dev/null +++ b/telephony/java/android/telephony/satellite/ISatelliteDatagramReceiverAck.aidl @@ -0,0 +1,41 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.satellite; + +import android.telephony.satellite.PointingInfo; +import android.telephony.satellite.SatelliteDatagram; + +/** + * Interface for satellite datagram receiver acknowledgement. + * @hide + */ +oneway interface ISatelliteDatagramReceiverAck { + /** + * This callback will be used by datagram receiver app to send ack back to + * Telephony. If the callback is not received within five minutes, + * then Telephony will resend the datagram again. + * + * @param datagramId An id that uniquely identifies datagram + * received by satellite datagram receiver app. + * This should match with datagramId passed in + * {@link SatelliteDatagramCallback#onSatelliteDatagramReceived( + * long, SatelliteDatagram, int, ISatelliteDatagramReceiverAck)}. + * Upon receiving the ack, Telephony will remove the datagram from + * the persistent memory. + */ + void acknowledgeSatelliteDatagramReceived(in long datagramId); +} diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java index 484b783e35e8..2c3884c81bfc 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java @@ -38,10 +38,12 @@ public class SatelliteDatagramCallback { } @Override - public void onSatelliteDatagrams(SatelliteDatagram[] datagrams) { + public void onSatelliteDatagramReceived(long datagramId, SatelliteDatagram datagram, + int pendingCount, ISatelliteDatagramReceiverAck callback) { final long callingIdentity = Binder.clearCallingIdentity(); try { - mExecutor.execute(() -> mLocalCallback.onSatelliteDatagrams(datagrams)); + mExecutor.execute(() -> mLocalCallback.onSatelliteDatagramReceived(datagramId, + datagram, pendingCount, callback)); } finally { restoreCallingIdentity(callingIdentity); } @@ -54,9 +56,14 @@ public class SatelliteDatagramCallback { /** * Called when there are incoming datagrams to be received. - * @param datagrams Datagrams to be received over satellite. + * @param datagramId An id that uniquely identifies incoming datagram. + * @param datagram datagram to be received over satellite. + * @param pendingCount Number of datagrams yet to be received by the app. + * @param callback This callback will be used by datagram receiver app to send ack back to + * Telephony. */ - public void onSatelliteDatagrams(SatelliteDatagram[] datagrams) { + public void onSatelliteDatagramReceived(long datagramId, SatelliteDatagram datagram, + int pendingCount, ISatelliteDatagramReceiverAck callback) { // Base Implementation } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 8fbf64070534..dbc0ed9c5a24 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -130,7 +130,7 @@ public class SatelliteManager { /** * Bundle key to get the response from - * {@link #requestMaxCharactersPerSatelliteTextMessage(Executor, OutcomeReceiver)}. + * {@link #requestMaxSizePerSendingDatagram(Executor, OutcomeReceiver)} . * @hide */ public static final String KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT = @@ -159,6 +159,13 @@ public class SatelliteManager { public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility"; /** + * Bundle key to get the respoonse from + * {@link #sendSatelliteDatagram(long, int, SatelliteDatagram, Executor, OutcomeReceiver)}. + * @hide + */ + public static final String KEY_SEND_SATELLITE_DATAGRAM = "send_satellite_datagram"; + + /** * The request was successfully processed. */ public static final int SATELLITE_ERROR_NONE = 0; @@ -541,8 +548,8 @@ public class SatelliteManager { @Retention(RetentionPolicy.SOURCE) public @interface SatelliteModemState {} - /** Datagram type indicating that the datagram to be sent or received is of type SOS SMS. */ - public static final int DATAGRAM_TYPE_SOS_SMS = 0; + /** Datagram type indicating that the datagram to be sent or received is of type SOS message. */ + public static final int DATAGRAM_TYPE_SOS_MESSAGE = 0; /** Datagram type indicating that the datagram to be sent or received is of type * location sharing. */ @@ -551,7 +558,7 @@ public class SatelliteManager { @IntDef( prefix = "DATAGRAM_TYPE_", value = { - DATAGRAM_TYPE_SOS_SMS, + DATAGRAM_TYPE_SOS_MESSAGE, DATAGRAM_TYPE_LOCATION_SHARING, }) @Retention(RetentionPolicy.SOURCE) @@ -651,19 +658,20 @@ public class SatelliteManager { } /** - * Request to get the maximum number of characters per text message on satellite. + * Request to get the maximum number of bytes per datagram that can be sent to satellite. * * @param executor The executor on which the callback will be called. * @param callback The callback object to which the result will be delivered. * If the request is successful, {@link OutcomeReceiver#onResult(Object)} - * will return the maximum number of characters per text message on satellite. + * will return the maximum number of bytes per datagram that can be sent to + * satellite. * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - public void requestMaxCharactersPerSatelliteTextMessage( + public void requestMaxSizePerSendingDatagram( @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Integer, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -693,7 +701,7 @@ public class SatelliteManager { } } }; - telephony.requestMaxCharactersPerSatelliteTextMessage(mSubId, receiver); + telephony.requestMaxSizePerSendingDatagram(mSubId, receiver); } else { throw new IllegalStateException("telephony service is null."); } @@ -1039,7 +1047,8 @@ public class SatelliteManager { * * This method requests modem to check if there are any pending datagrams to be received over * satellite. If there are any incoming datagrams, they will be received via - * {@link SatelliteDatagramCallback#onSatelliteDatagrams(SatelliteDatagram[])})}. + * {@link SatelliteDatagramCallback#onSatelliteDatagramReceived(long, SatelliteDatagram, int, + * ISatelliteDatagramReceiverAck)} * * @param executor The executor on which the result listener will be called. * @param resultListener Listener for the {@link SatelliteError} result of the operation. @@ -1076,39 +1085,60 @@ public class SatelliteManager { /** * Send datagram over satellite. * - * Gateway encodes SOS SMS or location sharing message into a datagram and passes it as input to - * this method. Datagram received here will be passed down to modem without any encoding or - * encryption. + * Gateway encodes SOS message or location sharing message into a datagram and passes it as + * input to this method. Datagram received here will be passed down to modem without any + * encoding or encryption. * + * @param datagramId An id that uniquely identifies datagram requested to be sent. * @param datagramType datagram type indicating whether the datagram is of type * SOS_SMS or LOCATION_SHARING. * @param datagram encoded gateway datagram which is encrypted by the caller. * Datagram will be passed down to modem without any encoding or encryption. * @param executor The executor on which the result listener will be called. - * @param resultListener Listener for the {@link SatelliteError} result of the operation. + * @param callback The callback object to which the result will be returned. + * If datagram is sent successfully, then + * {@link OutcomeReceiver#onResult(Object)} will return datagramId. + * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} + * will return a {@link SatelliteException} with the {@link SatelliteError}. * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - public void sendSatelliteDatagram(@DatagramType int datagramType, + public void sendSatelliteDatagram(long datagramId, @DatagramType int datagramType, @NonNull SatelliteDatagram datagram, @NonNull @CallbackExecutor Executor executor, - @SatelliteError @NonNull Consumer<Integer> resultListener) { + @NonNull OutcomeReceiver<Long, SatelliteException> callback) { Objects.requireNonNull(datagram); Objects.requireNonNull(executor); - Objects.requireNonNull(resultListener); + Objects.requireNonNull(callback); try { ITelephony telephony = getITelephony(); if (telephony != null) { - IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() { + ResultReceiver receiver = new ResultReceiver(null) { @Override - public void accept(int result) { - executor.execute(() -> Binder.withCleanCallingIdentity( - () -> resultListener.accept(result))); + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (resultCode == SATELLITE_ERROR_NONE) { + if (resultData.containsKey(KEY_SEND_SATELLITE_DATAGRAM)) { + long resultDatagramId = resultData + .getLong(KEY_SEND_SATELLITE_DATAGRAM); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onResult(resultDatagramId))); + } else { + loge("KEY_SEND_SATELLITE_DATAGRAM does not exist."); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onError( + new SatelliteException(SATELLITE_REQUEST_FAILED)))); + } + + } else { + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onError(new SatelliteException(resultCode)))); + } } }; - telephony.sendSatelliteDatagram(mSubId, datagramType, datagram, internalCallback); + telephony.sendSatelliteDatagram(mSubId, datagramId, datagramType, datagram, + receiver); } else { throw new IllegalStateException("telephony service is null."); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 62e087f3ca34..5bf55ef72bd0 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2772,15 +2772,15 @@ interface ITelephony { in ISatellitePositionUpdateCallback callback); /** - * Request to get the maximum number of characters per text message on satellite. + * Request to get the maximum number of bytes per datagram that can be sent to satellite. * * @param subId The subId of the subscription to get the maximum number of characters for. * @param receiver Result receiver to get the error code of the request and the requested - * maximum number of characters per text message on satellite. + * maximum number of bytes per datagram that can be sent to satellite. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") - void requestMaxCharactersPerSatelliteTextMessage(int subId, in ResultReceiver receiver); + void requestMaxSizePerSendingDatagram(int subId, in ResultReceiver receiver); /** * Register the subscription with a satellite provider. @@ -2912,14 +2912,16 @@ interface ITelephony { * Send datagram over satellite. * * @param subId The subId of the subscription to send satellite datagrams for. + * @param datagramId An id that uniquely identifies datagram requested to be sent. * @param datagramType Type of datagram. * @param datagram Datagram to send over satellite. - * @param callback The callback to get the error code of the request. + * @param receiver Result receiver to get the datagramId if datagram is sent successfully else + * error code of the request. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") - void sendSatelliteDatagram(int subId, int datagramType, in SatelliteDatagram datagram, - IIntegerConsumer callback); + void sendSatelliteDatagram(int subId, long datagramId, int datagramType, + in SatelliteDatagram datagram, in ResultReceiver receiver); /** * Request to get whether satellite communication is allowed for the current location. diff --git a/tools/processors/immutability/TEST_MAPPING b/tools/processors/immutability/TEST_MAPPING deleted file mode 100644 index 4e8e2386ad8d..000000000000 --- a/tools/processors/immutability/TEST_MAPPING +++ /dev/null @@ -1,7 +0,0 @@ -{ - "presubmit": [ - { - "name": "ImmutabilityAnnotationProcessorUnitTests" - } - ] -} diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java index 13084f47efe6..f3af06247576 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java @@ -59,7 +59,7 @@ public abstract class SharedConnectivityService extends Service { private static final String TAG = SharedConnectivityService.class.getSimpleName(); private static final boolean DEBUG = true; - private final Handler mHandler; + private Handler mHandler; private final List<ISharedConnectivityCallback> mCallbacks = new ArrayList<>(); // Used to find DeathRecipient when unregistering a callback to call unlinkToDeath. private final Map<ISharedConnectivityCallback, DeathRecipient> mDeathRecipientMap = @@ -71,14 +71,6 @@ public abstract class SharedConnectivityService extends Service { private TetherNetworkConnectionStatus mTetherNetworkConnectionStatus; private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus; - public SharedConnectivityService() { - mHandler = new Handler(getMainLooper()); - } - - public SharedConnectivityService(@NonNull Handler handler) { - mHandler = handler; - } - private final class DeathRecipient implements IBinder.DeathRecipient { ISharedConnectivityCallback mCallback; @@ -97,6 +89,7 @@ public abstract class SharedConnectivityService extends Service { @Nullable public final IBinder onBind(@NonNull Intent intent) { if (DEBUG) Log.i(TAG, "onBind intent=" + intent); + mHandler = new Handler(getMainLooper()); return new ISharedConnectivityService.Stub() { @Override public void registerCallback(ISharedConnectivityCallback callback) { diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java index fb8d7bf95ea0..d7f7fea4df3e 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java @@ -17,16 +17,21 @@ package android.net.wifi.sharedconnectivity.service; import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; +import android.content.Context; import android.content.Intent; import android.net.wifi.sharedconnectivity.app.KnownNetwork; import android.net.wifi.sharedconnectivity.app.TetherNetwork; -import android.os.Handler; -import android.os.test.TestLooper; +import android.os.Looper; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; +import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; /** * Unit tests for {@link android.net.wifi.sharedconnectivity.service.SharedConnectivityService}. @@ -34,6 +39,33 @@ import org.junit.Test; @SmallTest public class SharedConnectivityServiceTest { + @Mock + Context mContext; + + static class FakeSharedConnectivityService extends SharedConnectivityService { + public void attachBaseContext(Context context) { + super.attachBaseContext(context); + } + + @Override + public void onConnectTetherNetwork(@NonNull TetherNetwork network) {} + + @Override + public void onDisconnectTetherNetwork(@NonNull TetherNetwork network) {} + + @Override + public void onConnectKnownNetwork(@NonNull KnownNetwork network) {} + + @Override + public void onForgetKnownNetwork(@NonNull KnownNetwork network) {} + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); + } + /** * Verifies service returns */ @@ -51,18 +83,8 @@ public class SharedConnectivityServiceTest { } private SharedConnectivityService createService() { - return new SharedConnectivityService(new Handler(new TestLooper().getLooper())) { - @Override - public void onConnectTetherNetwork(TetherNetwork network) {} - - @Override - public void onDisconnectTetherNetwork(TetherNetwork network) {} - - @Override - public void onConnectKnownNetwork(KnownNetwork network) {} - - @Override - public void onForgetKnownNetwork(KnownNetwork network) {} - }; + FakeSharedConnectivityService service = new FakeSharedConnectivityService(); + service.attachBaseContext(mContext); + return service; } } |