/* * Copyright (C) 2021 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.dreams; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.WindowManager; import java.util.concurrent.Executor; /** * Basic implementation of for {@link IDreamOverlay} for testing. * @hide */ @TestApi public abstract class DreamOverlayService extends Service { private static final String TAG = "DreamOverlayService"; private static final boolean DEBUG = false; // The last client that started dreaming and hasn't ended private OverlayClient mCurrentClient; /** * Executor used to run callbacks that subclasses will implement. Any calls coming over Binder * from {@link OverlayClient} should perform the work they need to do on this executor. */ private Executor mExecutor; // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding // requests to the {@link DreamOverlayService} private static class OverlayClient extends IDreamOverlayClient.Stub { private final DreamOverlayService mService; private boolean mShowComplications; private ComponentName mDreamComponent; IDreamOverlayCallback mDreamOverlayCallback; OverlayClient(DreamOverlayService service) { mService = service; } @Override public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback, String dreamComponent, boolean shouldShowComplications) throws RemoteException { mDreamComponent = ComponentName.unflattenFromString(dreamComponent); mShowComplications = shouldShowComplications; mDreamOverlayCallback = callback; mService.startDream(this, params); } @Override public void wakeUp() { mService.wakeUp(this); } @Override public void endDream() { mService.endDream(this); } @Override public void comeToFront() { mService.comeToFront(this); } @Override public void onWakeRequested() { if (Flags.dreamWakeRedirect()) { mService.onWakeRequested(); } } private void requestExit() { try { mDreamOverlayCallback.onExitRequested(); } catch (RemoteException e) { Log.e(TAG, "Could not request exit:" + e); } } private void redirectWake(boolean redirect) { try { mDreamOverlayCallback.onRedirectWake(redirect); } catch (RemoteException e) { Log.e(TAG, "could not request redirect wake", e); } } private boolean shouldShowComplications() { return mShowComplications; } private ComponentName getComponent() { return mDreamComponent; } } private void startDream(OverlayClient client, WindowManager.LayoutParams params) { // Run on executor as this is a binder call from OverlayClient. mExecutor.execute(() -> { endDreamInternal(mCurrentClient); mCurrentClient = client; onStartDream(params); }); } private void endDream(OverlayClient client) { // Run on executor as this is a binder call from OverlayClient. mExecutor.execute(() -> endDreamInternal(client)); } private void endDreamInternal(OverlayClient client) { if (client == null || client != mCurrentClient) { return; } onEndDream(); mCurrentClient = null; } private void wakeUp(OverlayClient client) { // Run on executor as this is a binder call from OverlayClient. mExecutor.execute(() -> { if (mCurrentClient != client) { return; } onWakeUp(); }); } private void comeToFront(OverlayClient client) { mExecutor.execute(() -> { if (mCurrentClient != client) { return; } onComeToFront(); }); } private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() { @Override public void getClient(IDreamOverlayClientCallback callback) { try { callback.onDreamOverlayClient( new OverlayClient(DreamOverlayService.this)); } catch (RemoteException e) { Log.e(TAG, "could not send client to callback", e); } } }; public DreamOverlayService() { } /** * This constructor allows providing an executor to run callbacks on. * * @hide */ public DreamOverlayService(@NonNull Executor executor) { mExecutor = executor; } @Override public void onCreate() { super.onCreate(); if (mExecutor == null) { // If no executor was provided, use the main executor. onCreate is the earliest time // getMainExecutor is available. mExecutor = getMainExecutor(); } } @Nullable @Override public final IBinder onBind(@NonNull Intent intent) { return mDreamOverlay.asBinder(); } /** * This method is overridden by implementations to handle when the dream has started and the * window is ready to be interacted with. * * This callback will be run on the {@link Executor} provided in the constructor if provided, or * on the main executor if none was provided. * * @param layoutParams The {@link android.view.WindowManager.LayoutParams} associated with the * dream window. */ public abstract void onStartDream(@NonNull WindowManager.LayoutParams layoutParams); /** * This method is overridden by implementations to handle when the dream has been requested * to wakeup. * @hide */ public void onWakeUp() {} /** * This method is overridden by implementations to handle when the dream is coming to the front * (after having lost focus to something on top of it). * @hide */ public void onComeToFront() {} /** * This method is overridden by implementations to handle when the dream has ended. There may * be earlier signals leading up to this step, such as @{@link #onWakeUp(Runnable)}. * * This callback will be run on the {@link Executor} provided in the constructor if provided, or * on the main executor if none was provided. */ public void onEndDream() { } /** * This method is invoked to request the dream exit. */ public final void requestExit() { if (mCurrentClient == null) { throw new IllegalStateException("requested exit with no dream present"); } mCurrentClient.requestExit(); } /** * Called to inform the dream to redirect waking to this overlay rather than exiting. * @param redirect {@code true} if waking up should be redirected. {@code false} otherwise. * @hide */ @FlaggedApi(Flags.FLAG_DREAM_WAKE_REDIRECT) public final void redirectWake(boolean redirect) { if (!Flags.dreamWakeRedirect()) { return; } if (mCurrentClient == null) { throw new IllegalStateException("redirected wake with no dream present"); } mCurrentClient.redirectWake(redirect); } /** * Invoked when the dream has requested to exit. This is only called if the dream overlay * has explicitly requested exits to be redirected via {@link #redirectWake(boolean)}. * * @hide */ @FlaggedApi(Flags.FLAG_DREAM_WAKE_REDIRECT) public void onWakeRequested() { } /** * Returns whether to show complications on the dream overlay. */ public final boolean shouldShowComplications() { if (mCurrentClient == null) { throw new IllegalStateException( "requested if should show complication when no dream active"); } return mCurrentClient.shouldShowComplications(); } /** * Returns the active dream component. * @hide */ public final ComponentName getDreamComponent() { if (mCurrentClient == null) { throw new IllegalStateException("requested dream component when no dream active"); } return mCurrentClient.getComponent(); } }