diff options
| author | 2023-10-31 17:17:27 +0000 | |
|---|---|---|
| committer | 2023-10-31 17:17:27 +0000 | |
| commit | e529440c4297564360cab35e717cc7f003b9373c (patch) | |
| tree | fe4cef3e6befed514b7876a660fc714017390346 | |
| parent | 25693fe12e5bc6c39c19ddcd296d7810a5b1b330 (diff) | |
| parent | 0f897955025323766d7852c99bef2fa0726288e1 (diff) | |
Merge "Allow service to update content protection allowlist" into main
8 files changed, 190 insertions, 33 deletions
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index 06e86af0208f..78248d9775f4 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -138,7 +138,8 @@ public abstract class ContentCaptureService extends Service { new LocalDataShareAdapterResourceManager(); private Handler mHandler; - private IContentCaptureServiceCallback mCallback; + @Nullable private IContentCaptureServiceCallback mContentCaptureServiceCallback; + @Nullable private IContentProtectionAllowlistCallback mContentProtectionAllowlistCallback; private long mCallerMismatchTimeout = 1000; private long mLastCallerMismatchLog; @@ -215,6 +216,16 @@ public abstract class ContentCaptureService extends Service { Binder.getCallingUid(), events)); } + + @Override + public void onUpdateAllowlistRequest(IBinder callback) { + mHandler.sendMessage( + obtainMessage( + ContentCaptureService::handleOnUpdateAllowlistRequest, + ContentCaptureService.this, + Binder.getCallingUid(), + callback)); + } }; /** Binder that receives calls from the app in the content capture flow. */ @@ -278,14 +289,31 @@ public abstract class ContentCaptureService extends Service { */ public final void setContentCaptureWhitelist(@Nullable Set<String> packages, @Nullable Set<ComponentName> activities) { - final IContentCaptureServiceCallback callback = mCallback; - if (callback == null) { - Log.w(TAG, "setContentCaptureWhitelist(): no server callback"); + + IContentCaptureServiceCallback contentCaptureCallback = mContentCaptureServiceCallback; + IContentProtectionAllowlistCallback contentProtectionAllowlistCallback = + mContentProtectionAllowlistCallback; + + if (contentCaptureCallback == null && contentProtectionAllowlistCallback == null) { + Log.w(TAG, "setContentCaptureWhitelist(): missing both server callbacks"); + return; + } + + if (contentCaptureCallback != null) { + if (contentProtectionAllowlistCallback != null) { + throw new IllegalStateException("Have both server callbacks"); + } + try { + contentCaptureCallback.setContentCaptureWhitelist( + toList(packages), toList(activities)); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } return; } try { - callback.setContentCaptureWhitelist(toList(packages), toList(activities)); + contentProtectionAllowlistCallback.setAllowlist(toList(packages)); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -314,7 +342,7 @@ public abstract class ContentCaptureService extends Service { */ public final void setContentCaptureConditions(@NonNull String packageName, @Nullable Set<ContentCaptureCondition> conditions) { - final IContentCaptureServiceCallback callback = mCallback; + final IContentCaptureServiceCallback callback = mContentCaptureServiceCallback; if (callback == null) { Log.w(TAG, "setContentCaptureConditions(): no server callback"); return; @@ -425,7 +453,7 @@ public abstract class ContentCaptureService extends Service { public final void disableSelf() { if (sDebug) Log.d(TAG, "disableSelf()"); - final IContentCaptureServiceCallback callback = mCallback; + final IContentCaptureServiceCallback callback = mContentCaptureServiceCallback; if (callback == null) { Log.w(TAG, "disableSelf(): no server callback"); return; @@ -464,13 +492,14 @@ public abstract class ContentCaptureService extends Service { } private void handleOnConnected(@NonNull IBinder callback) { - mCallback = IContentCaptureServiceCallback.Stub.asInterface(callback); + mContentCaptureServiceCallback = IContentCaptureServiceCallback.Stub.asInterface(callback); onConnected(); } private void handleOnDisconnected() { onDisconnected(); - mCallback = null; + mContentCaptureServiceCallback = null; + mContentProtectionAllowlistCallback = null; } //TODO(b/111276913): consider caching the InteractionSessionId for the lifetime of the session, @@ -583,6 +612,16 @@ public abstract class ContentCaptureService extends Service { onContentCaptureEvent(sessionId, endEvent); } + private void handleOnUpdateAllowlistRequest(int uid, @NonNull IBinder callback) { + if (uid != Process.SYSTEM_UID) { + Log.e(TAG, "handleOnUpdateAllowlistRequest() not allowed for uid: " + uid); + return; + } + mContentProtectionAllowlistCallback = + IContentProtectionAllowlistCallback.Stub.asInterface(callback); + onConnected(); + } + private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) { onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData); } @@ -701,13 +740,14 @@ public abstract class ContentCaptureService extends Service { private void writeFlushMetrics(int sessionId, @Nullable ComponentName app, @NonNull FlushMetrics flushMetrics, @Nullable ContentCaptureOptions options, int flushReason) { - if (mCallback == null) { + if (mContentCaptureServiceCallback == null) { Log.w(TAG, "writeSessionFlush(): no server callback"); return; } try { - mCallback.writeSessionFlush(sessionId, app, flushMetrics, options, flushReason); + mContentCaptureServiceCallback.writeSessionFlush( + sessionId, app, flushMetrics, options, flushReason); } catch (RemoteException e) { Log.e(TAG, "failed to write flush metrics: " + e); } diff --git a/core/java/android/service/contentcapture/IContentProtectionAllowlistCallback.aidl b/core/java/android/service/contentcapture/IContentProtectionAllowlistCallback.aidl new file mode 100644 index 000000000000..8d5a15c1fdd2 --- /dev/null +++ b/core/java/android/service/contentcapture/IContentProtectionAllowlistCallback.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.contentcapture; + +import android.content.ComponentName; +import android.view.contentcapture.ContentCaptureCondition; +import android.service.contentcapture.FlushMetrics; +import android.content.ContentCaptureOptions; + +import java.util.List; + +/** + * Interface for the callback used by the content protection service to call the system server and + * update the allowlist. + * + * @hide + */ +oneway interface IContentProtectionAllowlistCallback { + void setAllowlist(in List<String> packages); +} diff --git a/core/java/android/service/contentcapture/IContentProtectionService.aidl b/core/java/android/service/contentcapture/IContentProtectionService.aidl index 4a13c3f63a33..25b248fcc8d4 100644 --- a/core/java/android/service/contentcapture/IContentProtectionService.aidl +++ b/core/java/android/service/contentcapture/IContentProtectionService.aidl @@ -17,6 +17,7 @@ package android.service.contentcapture; import android.content.pm.ParceledListSlice; +import android.os.IBinder; import android.view.contentcapture.ContentCaptureEvent; /** @@ -27,4 +28,6 @@ import android.view.contentcapture.ContentCaptureEvent; oneway interface IContentProtectionService { void onLoginDetected(in ParceledListSlice events); + + void onUpdateAllowlistRequest(in IBinder callback); } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 5f612d6b0009..970baf2ce264 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -411,6 +411,15 @@ public final class ContentCaptureManager { public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS = "content_protection_allowlist_timeout_ms"; + /** + * Sets the auto disconnect timeout for the content protection service in milliseconds. + * + * @hide + */ + // Unit can't be in the name in order to pass the checkstyle hook, line would be too long. + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT = + "content_protection_auto_disconnect_timeout_ms"; + /** @hide */ @TestApi public static final int LOGGING_LEVEL_OFF = 0; @@ -465,6 +474,8 @@ public final class ContentCaptureManager { public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS = 30000; /** @hide */ public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS = 250; + /** @hide */ + public static final long DEFAULT_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT_MS = 3000; private final Object mLock = new Object(); diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 9f4528b113e7..b2ff3c313837 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -22,9 +22,12 @@ import static android.service.contentcapture.ContentCaptureService.setClientStat import static android.view.contentcapture.ContentCaptureHelper.toList; import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS; import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE; import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG; import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD; import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION; @@ -227,6 +230,9 @@ public class ContentCaptureManagerService extends @GuardedBy("mLock") long mDevCfgContentProtectionAllowlistTimeoutMs; + @GuardedBy("mLock") + long mDevCfgContentProtectionAutoDisconnectTimeoutMs; + private final Executor mDataShareExecutor = Executors.newCachedThreadPool(); private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -435,14 +441,15 @@ public class ContentCaptureManagerService extends case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT: case ContentCaptureManager .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING: - case ContentCaptureManager - .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER: - case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE: + // Content protection below + case DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE: case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG: case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG: case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD: case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS: case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT: setFineTuneParamsFromDeviceConfig(); return; default: @@ -502,8 +509,7 @@ public class ContentCaptureManagerService extends mDevCfgContentProtectionBufferSize = DeviceConfig.getInt( DeviceConfig.NAMESPACE_CONTENT_CAPTURE, - ContentCaptureManager - .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE, ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE); contentProtectionRequiredGroupsConfig = DeviceConfig.getString( @@ -533,7 +539,12 @@ public class ContentCaptureManagerService extends DeviceConfig.NAMESPACE_CONTENT_CAPTURE, DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS, ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS); - + mDevCfgContentProtectionAutoDisconnectTimeoutMs = + DeviceConfig.getLong( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT_MS); contentProtectionAllowlistManagerOld = mContentProtectionAllowlistManager; if (verbose) { @@ -565,7 +576,9 @@ public class ContentCaptureManagerService extends + ", contentProtectionAllowlistDelayMs=" + contentProtectionAllowlistDelayMs + ", contentProtectionAllowlistTimeoutMs=" - + contentProtectionAllowlistTimeoutMs); + + contentProtectionAllowlistTimeoutMs + + ", contentProtectionAutoDisconnectTimeoutMs=" + + mDevCfgContentProtectionAutoDisconnectTimeoutMs); } } @@ -893,6 +906,9 @@ public class ContentCaptureManagerService extends pw.print(prefix2); pw.print("contentProtectionAllowlistTimeoutMs: "); pw.println(mDevCfgContentProtectionAllowlistTimeoutMs); + pw.print(prefix2); + pw.print("contentProtectionAutoDisconnectTimeoutMs: "); + pw.println(mDevCfgContentProtectionAutoDisconnectTimeoutMs); pw.print(prefix); pw.println("Global Options:"); mGlobalContentCaptureOptions.dump(prefix2, pw); @@ -962,12 +978,14 @@ public class ContentCaptureManagerService extends @Nullable public RemoteContentProtectionService createRemoteContentProtectionService() { ComponentName componentName; + long autoDisconnectTimeoutMs; synchronized (mLock) { if (!mDevCfgEnableContentProtectionReceiver || mContentProtectionServiceComponentName == null) { return null; } componentName = mContentProtectionServiceComponentName; + autoDisconnectTimeoutMs = mDevCfgContentProtectionAutoDisconnectTimeoutMs; } // Check permissions by trying to construct {@link ContentCaptureServiceInfo} @@ -978,19 +996,20 @@ public class ContentCaptureManagerService extends return null; } - return createRemoteContentProtectionService(componentName); + return createRemoteContentProtectionService(componentName, autoDisconnectTimeoutMs); } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @NonNull protected RemoteContentProtectionService createRemoteContentProtectionService( - @NonNull ComponentName componentName) { + @NonNull ComponentName componentName, long autoDisconnectTimeoutMs) { return new RemoteContentProtectionService( getContext(), componentName, UserHandle.getCallingUserId(), - isBindInstantServiceAllowed()); + isBindInstantServiceAllowed(), + autoDisconnectTimeoutMs); } /** @hide */ diff --git a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java index dd5545dcccc7..bc11fc3cdaf0 100644 --- a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java +++ b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java @@ -22,14 +22,13 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.service.contentcapture.ContentCaptureService; +import android.service.contentcapture.IContentProtectionAllowlistCallback; import android.service.contentcapture.IContentProtectionService; import android.util.Slog; import android.view.contentcapture.ContentCaptureEvent; import com.android.internal.infra.ServiceConnector; -import java.time.Duration; - /** * Connector for the remote content protection service. * @@ -40,15 +39,16 @@ public class RemoteContentProtectionService private static final String TAG = RemoteContentProtectionService.class.getSimpleName(); - private static final Duration AUTO_DISCONNECT_TIMEOUT = Duration.ofSeconds(3); - @NonNull private final ComponentName mComponentName; + private final long mAutoDisconnectTimeoutMs; + public RemoteContentProtectionService( @NonNull Context context, @NonNull ComponentName componentName, int userId, - boolean bindAllowInstant) { + boolean bindAllowInstant, + long autoDisconnectTimeoutMs) { super( context, new Intent(ContentCaptureService.PROTECTION_SERVICE_INTERFACE) @@ -57,11 +57,12 @@ public class RemoteContentProtectionService userId, IContentProtectionService.Stub::asInterface); mComponentName = componentName; + mAutoDisconnectTimeoutMs = autoDisconnectTimeoutMs; } @Override // from ServiceConnector.Impl protected long getAutoDisconnectTimeoutMs() { - return AUTO_DISCONNECT_TIMEOUT.toMillis(); + return mAutoDisconnectTimeoutMs; } @Override // from ServiceConnector.Impl @@ -75,7 +76,13 @@ public class RemoteContentProtectionService + (isConnected ? "connected" : "disconnected")); } + /** Calls the remote service when login is detected. */ public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) { run(service -> service.onLoginDetected(events)); } + + /** Calls the remote service with a request to update allowlist. */ + public void onUpdateAllowlistRequest(@NonNull IContentProtectionAllowlistCallback callback) { + run(service -> service.onUpdateAllowlistRequest(callback.asBinder())); + } } diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java index 9a5241ea242a..6d13d876b06b 100644 --- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java @@ -662,7 +662,7 @@ public class ContentCaptureManagerServiceTest { @Override protected RemoteContentProtectionService createRemoteContentProtectionService( - @NonNull ComponentName componentName) { + @NonNull ComponentName componentName, long autoDisconnectTimeoutMs) { mRemoteContentProtectionServicesCreated++; return mMockRemoteContentProtectionService; } diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java index 9135ef3a1286..6a7e2865fb32 100644 --- a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java @@ -21,12 +21,16 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.pm.ParceledListSlice; +import android.os.Binder; +import android.os.IBinder; import android.os.UserHandle; +import android.service.contentcapture.IContentProtectionAllowlistCallback; import android.service.contentcapture.IContentProtectionService; import android.view.contentcapture.ContentCaptureEvent; @@ -57,21 +61,27 @@ import org.mockito.junit.MockitoRule; @SmallTest public class RemoteContentProtectionServiceTest { - private final Context mContext = ApplicationProvider.getApplicationContext(); + private static final long AUTO_DISCONNECT_TIMEOUT_MS = 12345L; + + private static final IBinder BINDER = new Binder(); + + private static final Context CONTEXT = ApplicationProvider.getApplicationContext(); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Mock private IContentProtectionService mMockContentProtectionService; + @Mock private IContentProtectionAllowlistCallback mMockContentProtectionAllowlistCallback; + private RemoteContentProtectionService mRemoteContentProtectionService; private int mConnectCallCount = 0; @Before public void setup() { - ComponentName componentName = new ComponentName(mContext.getPackageName(), "TestClass"); + ComponentName componentName = new ComponentName(CONTEXT.getPackageName(), "TestClass"); mRemoteContentProtectionService = - new TestRemoteContentProtectionService(mContext, componentName); + new TestRemoteContentProtectionService(CONTEXT, componentName); } @Test @@ -84,7 +94,7 @@ public class RemoteContentProtectionServiceTest { public void getAutoDisconnectTimeoutMs() { long actual = mRemoteContentProtectionService.getAutoDisconnectTimeoutMs(); - assertThat(actual).isEqualTo(3000L); + assertThat(actual).isEqualTo(AUTO_DISCONNECT_TIMEOUT_MS); } @Test @@ -99,10 +109,43 @@ public class RemoteContentProtectionServiceTest { verify(mMockContentProtectionService).onLoginDetected(events); } + @Test + public void onUpdateAllowlistRequest() throws Exception { + when(mMockContentProtectionAllowlistCallback.asBinder()).thenReturn(BINDER); + + mRemoteContentProtectionService.onUpdateAllowlistRequest( + mMockContentProtectionAllowlistCallback); + + verify(mMockContentProtectionService).onUpdateAllowlistRequest(BINDER); + } + + @Test + public void onServiceConnectionStatusChanged_connected_noSideEffects() { + mRemoteContentProtectionService.onServiceConnectionStatusChanged( + mMockContentProtectionService, /* isConnected= */ true); + + verifyZeroInteractions(mMockContentProtectionService); + assertThat(mConnectCallCount).isEqualTo(0); + } + + @Test + public void onServiceConnectionStatusChanged_disconnected_noSideEffects() { + mRemoteContentProtectionService.onServiceConnectionStatusChanged( + mMockContentProtectionService, /* isConnected= */ false); + + verifyZeroInteractions(mMockContentProtectionService); + assertThat(mConnectCallCount).isEqualTo(0); + } + private final class TestRemoteContentProtectionService extends RemoteContentProtectionService { TestRemoteContentProtectionService(Context context, ComponentName componentName) { - super(context, componentName, UserHandle.myUserId(), /* bindAllowInstant= */ false); + super( + context, + componentName, + UserHandle.myUserId(), + /* bindAllowInstant= */ false, + AUTO_DISCONNECT_TIMEOUT_MS); } @Override // from ServiceConnector |