From 8ca4e81df7f1ef59245ddc6246d41ab039a8ab91 Mon Sep 17 00:00:00 2001 From: Darryl L Johnson Date: Fri, 30 Oct 2020 14:49:34 -0700 Subject: Fix DeviceStateManagerServiceTests and add to presubmit. 1. The addition of the CONTROL_DEVICE_STATE permission broke the test as the permission was not granted to the test APK. 2. Publishing of the binder service was previously wrapped with a SecurityException catch which failed gracefully in the test. This prevents registering the binder service in the test. Test: atest DeviceStateManagerServiceTests Bug: 159401801 Change-Id: Ifc24ba370a30a86121ca1fa531b8a396f7513b97 --- .../com/android/server/devicestate/DeviceStateManagerService.java | 4 ++-- services/tests/servicestests/AndroidManifest.xml | 1 + .../com/android/server/devicestate/DeviceStateManagerServiceTest.java | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index fe6e60f6159c..c51c38de9c65 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -16,8 +16,8 @@ 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; @@ -96,11 +96,11 @@ public final class DeviceStateManagerService extends SystemService { DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) { super(context); mDeviceStatePolicy = policy; + mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener()); } @Override public void onStart() { - mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener()); publishBinderService(Context.DEVICE_STATE_SERVICE, new BinderService()); } 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 @@ + * Run with atest DeviceStateManagerServiceTest. */ +@Presubmit @RunWith(AndroidJUnit4.class) public final class DeviceStateManagerServiceTest { private static final int DEFAULT_DEVICE_STATE = 0; @@ -48,7 +51,6 @@ public final class DeviceStateManagerServiceTest { mProvider = new TestDeviceStateProvider(); mPolicy = new TestDeviceStatePolicy(mProvider); mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy); - mService.onStart(); } @Test -- cgit v1.2.3-59-g8ed1b From 73676b48bee6ed39606627b602e993f8bde34f24 Mon Sep 17 00:00:00 2001 From: Darryl L Johnson Date: Thu, 24 Sep 2020 14:51:31 -0700 Subject: Add onDeviceStateChanged() callback to DeviceStateManager. This change introduces the DeviceStateCallback API to DeviceStateManager which allows processes outside system_server (as well as system_server) to receive notifications about changes in the system's device state. Test: atest DeviceStateManagerServiceTest Bug: 159401801 Change-Id: I9ccdbd8c4a51858a13b0152ed39a8bf20e41e64f --- .../hardware/devicestate/DeviceStateManager.java | 46 ++++++- .../devicestate/DeviceStateManagerGlobal.java | 125 ++++++++++++++++++- .../hardware/devicestate/IDeviceStateManager.aidl | 6 +- .../devicestate/IDeviceStateManagerCallback.aidl | 22 ++++ core/tests/devicestatetests/Android.bp | 26 ++++ core/tests/devicestatetests/AndroidManifest.xml | 29 +++++ core/tests/devicestatetests/AndroidTest.xml | 31 +++++ .../devicestate/DeviceStateManagerGlobalTest.java | 135 +++++++++++++++++++++ .../devicestate/DeviceStateManagerService.java | 129 +++++++++++++++++++- .../devicestate/DeviceStateManagerServiceTest.java | 52 ++++++++ 10 files changed, 596 insertions(+), 5 deletions(-) create mode 100644 core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl create mode 100644 core/tests/devicestatetests/Android.bp create mode 100644 core/tests/devicestatetests/AndroidManifest.xml create mode 100644 core/tests/devicestatetests/AndroidTest.xml create mode 100644 core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java 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. + *

+ * 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 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 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 @@ + + + + + + + + + + + + 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 @@ + + + + + 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}. + *

+ * Run with atest DeviceStateManagerGlobalTest. + */ +@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 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 c51c38de9c65..3172a04e9a3d 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -23,11 +23,15 @@ 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 mCallbacks = new SparseArray<>(); + public DeviceStateManagerService(@NonNull Context context) { this(context, new DeviceStatePolicyImpl()); } @@ -97,11 +108,12 @@ public final class DeviceStateManagerService extends SystemService { super(context); mDeviceStatePolicy = policy; mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener()); + mBinderService = new BinderService(); } @Override public void onStart() { - 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 { *

*/ 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 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,8 +448,47 @@ 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) { 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 0e58be3538e0..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,11 @@ 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; @@ -30,6 +33,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import javax.annotation.Nullable; + /** * Unit tests for {@link DeviceStateManagerService}. *

@@ -189,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; @@ -264,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; + } + } } -- cgit v1.2.3-59-g8ed1b