diff options
| author | 2020-11-06 20:38:27 +0000 | |
|---|---|---|
| committer | 2020-11-06 20:38:27 +0000 | |
| commit | e4cbd037823b5e12555968fed4aa2fbdfaa62c46 (patch) | |
| tree | 48e69538c093efd071afaf76f0090613d94ad8c8 | |
| parent | 31012130c6edc52d8bd8b13964a0184f3bb8ae30 (diff) | |
| parent | 73676b48bee6ed39606627b602e993f8bde34f24 (diff) | |
Merge changes I9ccdbd8c,Ifc24ba37
* changes:
Add onDeviceStateChanged() callback to DeviceStateManager.
Fix DeviceStateManagerServiceTests and add to presubmit.
11 files changed, 602 insertions, 8 deletions
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index a52f983ff2a7..29a6ee278d9c 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -16,9 +16,12 @@ package android.hardware.devicestate; +import android.annotation.NonNull; import android.annotation.SystemService; import android.content.Context; +import java.util.concurrent.Executor; + /** * Manages the state of the system for devices with user-configurable hardware like a foldable * phone. @@ -33,6 +36,47 @@ public final class DeviceStateManager { private DeviceStateManagerGlobal mGlobal; public DeviceStateManager() { - mGlobal = DeviceStateManagerGlobal.getInstance(); + DeviceStateManagerGlobal global = DeviceStateManagerGlobal.getInstance(); + if (global == null) { + throw new IllegalStateException( + "Failed to get instance of global device state manager."); + } + mGlobal = global; + } + + /** + * Registers a listener to receive notifications about changes in device state. + * + * @param listener the listener to register. + * @param executor the executor to process notifications. + * + * @see DeviceStateListener + */ + public void registerDeviceStateListener(@NonNull DeviceStateListener listener, + @NonNull Executor executor) { + mGlobal.registerDeviceStateListener(listener, executor); + } + + /** + * Unregisters a listener previously registered with + * {@link #registerDeviceStateListener(DeviceStateListener, Executor)}. + */ + public void unregisterDeviceStateListener(@NonNull DeviceStateListener listener) { + mGlobal.unregisterDeviceStateListener(listener); + } + + /** + * Listens for changes in device states. + */ + public interface DeviceStateListener { + /** + * Called in response to device state changes. + * <p> + * Guaranteed to be called once on registration of the listener with the + * initial value and then on every subsequent change in device state. + * + * @param deviceState the new device state. + */ + void onDeviceStateChanged(int deviceState); } } diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java index 4e7cf4af118d..c8905038d056 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java +++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java @@ -19,16 +19,26 @@ package android.hardware.devicestate; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.devicestate.DeviceStateManager.DeviceStateListener; import android.os.IBinder; +import android.os.RemoteException; import android.os.ServiceManager; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; + +import java.util.ArrayList; +import java.util.concurrent.Executor; + /** * Provides communication with the device state system service on behalf of applications. * * @see DeviceStateManager * @hide */ -final class DeviceStateManagerGlobal { +@VisibleForTesting(visibility = Visibility.PACKAGE) +public final class DeviceStateManagerGlobal { private static DeviceStateManagerGlobal sInstance; /** @@ -49,10 +59,121 @@ final class DeviceStateManagerGlobal { } } + private final Object mLock = new Object(); @NonNull private final IDeviceStateManager mDeviceStateManager; + @Nullable + private DeviceStateManagerCallback mCallback; - private DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) { + @GuardedBy("mLock") + private final ArrayList<DeviceStateListenerWrapper> mListeners = new ArrayList<>(); + @Nullable + @GuardedBy("mLock") + private Integer mLastReceivedState; + + @VisibleForTesting + public DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) { mDeviceStateManager = deviceStateManager; } + + /** + * Registers a listener to receive notifications about changes in device state. + * + * @see DeviceStateManager#registerDeviceStateListener(DeviceStateListener, Executor) + */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public void registerDeviceStateListener(@NonNull DeviceStateListener listener, + @NonNull Executor executor) { + Integer stateToReport; + DeviceStateListenerWrapper wrapper; + synchronized (mLock) { + registerCallbackIfNeededLocked(); + + int index = findListenerLocked(listener); + if (index != -1) { + // This listener is already registered. + return; + } + + wrapper = new DeviceStateListenerWrapper(listener, executor); + mListeners.add(wrapper); + stateToReport = mLastReceivedState; + } + + if (stateToReport != null) { + // Notify the listener with the most recent device state from the server. If the state + // to report is null this is likely the first listener added and we're still waiting + // from the callback from the server. + wrapper.notifyDeviceStateChanged(stateToReport); + } + } + + /** + * Unregisters a listener previously registered with + * {@link #registerDeviceStateListener(DeviceStateListener, Executor)}. + * + * @see DeviceStateManager#registerDeviceStateListener(DeviceStateListener, Executor) + */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public void unregisterDeviceStateListener(DeviceStateListener listener) { + synchronized (mLock) { + int indexToRemove = findListenerLocked(listener); + if (indexToRemove != -1) { + mListeners.remove(indexToRemove); + } + } + } + + private void registerCallbackIfNeededLocked() { + if (mCallback == null) { + mCallback = new DeviceStateManagerCallback(); + try { + mDeviceStateManager.registerCallback(mCallback); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + } + + private int findListenerLocked(DeviceStateListener listener) { + for (int i = 0; i < mListeners.size(); i++) { + if (mListeners.get(i).mDeviceStateListener.equals(listener)) { + return i; + } + } + return -1; + } + + private void handleDeviceStateChanged(int newDeviceState) { + ArrayList<DeviceStateListenerWrapper> listeners; + synchronized (mLock) { + mLastReceivedState = newDeviceState; + listeners = new ArrayList<>(mListeners); + } + + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).notifyDeviceStateChanged(newDeviceState); + } + } + + private final class DeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub { + @Override + public void onDeviceStateChanged(int deviceState) { + handleDeviceStateChanged(deviceState); + } + } + + private static final class DeviceStateListenerWrapper { + private final DeviceStateListener mDeviceStateListener; + private final Executor mExecutor; + + DeviceStateListenerWrapper(DeviceStateListener listener, Executor executor) { + mDeviceStateListener = listener; + mExecutor = executor; + } + + void notifyDeviceStateChanged(int newDeviceState) { + mExecutor.execute(() -> mDeviceStateListener.onDeviceStateChanged(newDeviceState)); + } + } } diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl index 24913e9b2d23..a157b3311ca5 100644 --- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl +++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl @@ -16,5 +16,9 @@ package android.hardware.devicestate; +import android.hardware.devicestate.IDeviceStateManagerCallback; + /** @hide */ -interface IDeviceStateManager {} +interface IDeviceStateManager { + void registerCallback(in IDeviceStateManagerCallback callback); +} diff --git a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl new file mode 100644 index 000000000000..d1c581361b62 --- /dev/null +++ b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2020, 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.hardware.devicestate; + +/** @hide */ +interface IDeviceStateManagerCallback { + oneway void onDeviceStateChanged(int deviceState); +} diff --git a/core/tests/devicestatetests/Android.bp b/core/tests/devicestatetests/Android.bp new file mode 100644 index 000000000000..409b77bc399e --- /dev/null +++ b/core/tests/devicestatetests/Android.bp @@ -0,0 +1,26 @@ +// Copyright (C) 2020 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. + +android_test { + name: "FrameworksCoreDeviceStateManagerTests", + // Include all test java files + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.test.rules", + "frameworks-base-testutils", + ], + libs: ["android.test.runner"], + platform_apis: true, + certificate: "platform", +} diff --git a/core/tests/devicestatetests/AndroidManifest.xml b/core/tests/devicestatetests/AndroidManifest.xml new file mode 100644 index 000000000000..acd6e7b2759a --- /dev/null +++ b/core/tests/devicestatetests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.coretests.devicestate" + android:sharedUserId="android.uid.system" > + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.coretests.devicestate" + android:label="Device State Manager Tests"/> + +</manifest> diff --git a/core/tests/devicestatetests/AndroidTest.xml b/core/tests/devicestatetests/AndroidTest.xml new file mode 100644 index 000000000000..9e38afddbd96 --- /dev/null +++ b/core/tests/devicestatetests/AndroidTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 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. +--> + +<configuration description="Runs Device State Manager Tests."> + <option name="test-suite-tag" value="apct"/> + <option name="test-suite-tag" value="apct-instrumentation"/> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="FrameworksCoreDeviceStateManagerTests.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.coretests.devicestate" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false" /> + </test> +</configuration> diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java new file mode 100644 index 000000000000..36f01f9a951d --- /dev/null +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2020 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.hardware.devicestate; + +import static junit.framework.Assert.assertEquals; + +import android.annotation.Nullable; +import android.os.RemoteException; + +import androidx.test.filters.SmallTest; + +import com.android.internal.util.ConcurrentUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.HashSet; +import java.util.Set; + +/** + * Unit tests for {@link DeviceStateManagerGlobal}. + * <p/> + * Run with <code>atest DeviceStateManagerGlobalTest</code>. + */ +@RunWith(JUnit4.class) +@SmallTest +public final class DeviceStateManagerGlobalTest { + private TestDeviceStateManagerService mService; + private DeviceStateManagerGlobal mDeviceStateManagerGlobal; + + @Before + public void setUp() { + mService = new TestDeviceStateManagerService(); + mDeviceStateManagerGlobal = new DeviceStateManagerGlobal(mService); + } + + @Test + public void registerListener() { + mService.setDeviceState(0); + + TestDeviceStateListener listener1 = new TestDeviceStateListener(); + TestDeviceStateListener listener2 = new TestDeviceStateListener(); + + mDeviceStateManagerGlobal.registerDeviceStateListener(listener1, + ConcurrentUtils.DIRECT_EXECUTOR); + mDeviceStateManagerGlobal.registerDeviceStateListener(listener2, + ConcurrentUtils.DIRECT_EXECUTOR); + assertEquals(0, listener1.getLastReportedState().intValue()); + assertEquals(0, listener2.getLastReportedState().intValue()); + + mService.setDeviceState(1); + assertEquals(1, listener1.getLastReportedState().intValue()); + assertEquals(1, listener2.getLastReportedState().intValue()); + } + + @Test + public void unregisterListener() { + mService.setDeviceState(0); + + TestDeviceStateListener listener = new TestDeviceStateListener(); + + mDeviceStateManagerGlobal.registerDeviceStateListener(listener, + ConcurrentUtils.DIRECT_EXECUTOR); + assertEquals(0, listener.getLastReportedState().intValue()); + + mDeviceStateManagerGlobal.unregisterDeviceStateListener(listener); + + mService.setDeviceState(1); + assertEquals(0, listener.getLastReportedState().intValue()); + } + + private final class TestDeviceStateListener implements DeviceStateManager.DeviceStateListener { + @Nullable + private Integer mLastReportedDeviceState; + + @Override + public void onDeviceStateChanged(int deviceState) { + mLastReportedDeviceState = deviceState; + } + + @Nullable + public Integer getLastReportedState() { + return mLastReportedDeviceState; + } + } + + private final class TestDeviceStateManagerService extends IDeviceStateManager.Stub { + private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; + private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>(); + + @Override + public void registerCallback(IDeviceStateManagerCallback callback) { + if (mCallbacks.contains(callback)) { + throw new SecurityException("Callback is already registered."); + } + + mCallbacks.add(callback); + try { + callback.onDeviceStateChanged(mDeviceState); + } catch (RemoteException e) { + // Do nothing. Should never happen. + } + } + + public void setDeviceState(int deviceState) { + boolean stateChanged = mDeviceState != deviceState; + mDeviceState = deviceState; + if (stateChanged) { + for (IDeviceStateManagerCallback callback : mCallbacks) { + try { + callback.onDeviceStateChanged(mDeviceState); + } catch (RemoteException e) { + // Do nothing. Should never happen. + } + } + } + } + } +} diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index fe6e60f6159c..3172a04e9a3d 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -16,18 +16,22 @@ package com.android.server.devicestate; -import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; import static android.Manifest.permission.CONTROL_DEVICE_STATE; +import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; import android.annotation.NonNull; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.devicestate.IDeviceStateManager; +import android.hardware.devicestate.IDeviceStateManagerCallback; import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.util.IntArray; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -37,6 +41,7 @@ import com.android.server.policy.DeviceStatePolicyImpl; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; /** @@ -66,6 +71,8 @@ public final class DeviceStateManagerService extends SystemService { private final Object mLock = new Object(); @NonNull private final DeviceStatePolicy mDeviceStatePolicy; + @NonNull + private final BinderService mBinderService; @GuardedBy("mLock") private IntArray mSupportedDeviceStates; @@ -88,6 +95,10 @@ public final class DeviceStateManagerService extends SystemService { @GuardedBy("mLock") private int mRequestedOverrideState = INVALID_DEVICE_STATE; + // List of registered callbacks indexed by process id. + @GuardedBy("mLock") + private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>(); + public DeviceStateManagerService(@NonNull Context context) { this(context, new DeviceStatePolicyImpl()); } @@ -96,12 +107,13 @@ public final class DeviceStateManagerService extends SystemService { DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) { super(context); mDeviceStatePolicy = policy; + mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener()); + mBinderService = new BinderService(); } @Override public void onStart() { - mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener()); - publishBinderService(Context.DEVICE_STATE_SERVICE, new BinderService()); + publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService); } /** @@ -186,6 +198,11 @@ public final class DeviceStateManagerService extends SystemService { } } + @VisibleForTesting + IDeviceStateManager getBinderService() { + return mBinderService; + } + private void updateSupportedStates(int[] supportedDeviceStates) { // Must ensure sorted as isSupportedStateLocked() impl uses binary search. Arrays.sort(supportedDeviceStates, 0, supportedDeviceStates.length); @@ -310,18 +327,81 @@ public final class DeviceStateManagerService extends SystemService { * </p> */ private void commitPendingState() { + // Update the current state. + int newState; synchronized (mLock) { if (DEBUG) { Slog.d(TAG, "Committing state: " + mPendingState); } mCommittedState = mPendingState; + newState = mCommittedState; mPendingState = INVALID_DEVICE_STATE; updatePendingStateLocked(); } + // Notify callbacks of a change. + notifyDeviceStateChanged(newState); + + // Try to configure the next state if needed. notifyPolicyIfNeeded(); } + private void notifyDeviceStateChanged(int deviceState) { + if (Thread.holdsLock(mLock)) { + throw new IllegalStateException( + "Attempting to notify callbacks with service lock held."); + } + + // Grab the lock and copy the callbacks. + ArrayList<CallbackRecord> callbacks; + synchronized (mLock) { + if (mCallbacks.size() == 0) { + return; + } + + callbacks = new ArrayList<>(); + for (int i = 0; i < mCallbacks.size(); i++) { + callbacks.add(mCallbacks.valueAt(i)); + } + } + + // After releasing the lock, send the notifications out. + for (int i = 0; i < callbacks.size(); i++) { + callbacks.get(i).notifyDeviceStateAsync(deviceState); + } + } + + private void registerCallbackInternal(IDeviceStateManagerCallback callback, int callingPid) { + int currentState; + CallbackRecord record; + // Grab the lock to register the callback and get the current state. + synchronized (mLock) { + if (mCallbacks.contains(callingPid)) { + throw new SecurityException("The calling process has already registered an" + + " IDeviceStateManagerCallback."); + } + + record = new CallbackRecord(callback, callingPid); + try { + callback.asBinder().linkToDeath(record, 0); + } catch (RemoteException ex) { + throw new RuntimeException(ex); + } + + mCallbacks.put(callingPid, record); + currentState = mCommittedState; + } + + // Notify the callback of the state at registration. + record.notifyDeviceStateAsync(currentState); + } + + private void unregisterCallbackInternal(CallbackRecord record) { + synchronized (mLock) { + mCallbacks.remove(record.mPid); + } + } + private void dumpInternal(PrintWriter pw) { pw.println("DEVICE STATE MANAGER (dumpsys device_state)"); @@ -330,6 +410,14 @@ public final class DeviceStateManagerService extends SystemService { pw.println(" mPendingState=" + toString(mPendingState)); pw.println(" mRequestedState=" + toString(mRequestedState)); pw.println(" mRequestedOverrideState=" + toString(mRequestedOverrideState)); + + final int callbackCount = mCallbacks.size(); + pw.println(); + pw.println("Callbacks: size=" + callbackCount); + for (int i = 0; i < callbackCount; i++) { + CallbackRecord callback = mCallbacks.valueAt(i); + pw.println(" " + i + ": mPid=" + callback.mPid); + } } } @@ -360,9 +448,48 @@ public final class DeviceStateManagerService extends SystemService { } } + private final class CallbackRecord implements IBinder.DeathRecipient { + private final IDeviceStateManagerCallback mCallback; + private final int mPid; + + CallbackRecord(IDeviceStateManagerCallback callback, int pid) { + mCallback = callback; + mPid = pid; + } + + @Override + public void binderDied() { + unregisterCallbackInternal(this); + } + + public void notifyDeviceStateAsync(int devicestate) { + try { + mCallback.onDeviceStateChanged(devicestate); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + " that device state changed.", + ex); + } + } + } + /** Implementation of {@link IDeviceStateManager} published as a binder service. */ private final class BinderService extends IDeviceStateManager.Stub { @Override // Binder call + public void registerCallback(IDeviceStateManagerCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("Device state callback must not be null."); + } + + final int callingPid = Binder.getCallingPid(); + final long token = Binder.clearCallingIdentity(); + try { + registerCallbackInternal(callback, callingPid); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver result) { new DeviceStateManagerShellCommand(DeviceStateManagerService.this) diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 26fd0a21f10f..6ead95ca03b6 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -85,6 +85,7 @@ <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE"/> <uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON"/> + <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE"/> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index 9b182a71f419..95aac60f65ff 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -19,8 +19,13 @@ package com.android.server.devicestate; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertThrows; +import android.hardware.devicestate.IDeviceStateManagerCallback; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -28,11 +33,14 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import javax.annotation.Nullable; + /** * Unit tests for {@link DeviceStateManagerService}. * <p/> * Run with <code>atest DeviceStateManagerServiceTest</code>. */ +@Presubmit @RunWith(AndroidJUnit4.class) public final class DeviceStateManagerServiceTest { private static final int DEFAULT_DEVICE_STATE = 0; @@ -48,7 +56,6 @@ public final class DeviceStateManagerServiceTest { mProvider = new TestDeviceStateProvider(); mPolicy = new TestDeviceStatePolicy(mProvider); mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy); - mService.onStart(); } @Test @@ -187,6 +194,38 @@ public final class DeviceStateManagerServiceTest { assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE); } + @Test + public void registerCallback() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + + mProvider.notifyRequestState(OTHER_DEVICE_STATE); + assertNotNull(callback.getLastNotifiedValue()); + assertEquals(callback.getLastNotifiedValue().intValue(), OTHER_DEVICE_STATE); + + mProvider.notifyRequestState(DEFAULT_DEVICE_STATE); + assertEquals(callback.getLastNotifiedValue().intValue(), DEFAULT_DEVICE_STATE); + + mPolicy.blockConfigure(); + mProvider.notifyRequestState(OTHER_DEVICE_STATE); + // The callback should not have been notified of the state change as the policy is still + // pending callback. + assertEquals(callback.getLastNotifiedValue().intValue(), DEFAULT_DEVICE_STATE); + + mPolicy.resumeConfigure(); + // Now that the policy is finished processing the callback should be notified of the state + // change. + assertEquals(callback.getLastNotifiedValue().intValue(), OTHER_DEVICE_STATE); + } + + @Test + public void registerCallback_emitsInitialValue() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + assertNotNull(callback.getLastNotifiedValue()); + assertEquals(callback.getLastNotifiedValue().intValue(), DEFAULT_DEVICE_STATE); + } + private static final class TestDeviceStatePolicy implements DeviceStatePolicy { private final DeviceStateProvider mProvider; private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE; @@ -262,4 +301,19 @@ public final class DeviceStateManagerServiceTest { mListener.onStateChanged(state); } } + + private static final class TestDeviceStateManagerCallback extends + IDeviceStateManagerCallback.Stub { + Integer mLastNotifiedValue; + + @Override + public void onDeviceStateChanged(int deviceState) { + mLastNotifiedValue = deviceState; + } + + @Nullable + Integer getLastNotifiedValue() { + return mLastNotifiedValue; + } + } } |