1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.dreams;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.TestApi;
22 import android.app.Service;
23 import android.content.ComponentName;
24 import android.content.Intent;
25 import android.os.IBinder;
26 import android.os.RemoteException;
27 import android.util.Log;
28 import android.view.WindowManager;
29 
30 import java.util.concurrent.Executor;
31 
32 
33 /**
34  * Basic implementation of for {@link IDreamOverlay} for testing.
35  * @hide
36  */
37 @TestApi
38 public abstract class DreamOverlayService extends Service {
39     private static final String TAG = "DreamOverlayService";
40     private static final boolean DEBUG = false;
41 
42     // The last client that started dreaming and hasn't ended
43     private OverlayClient mCurrentClient;
44 
45     /**
46      * Executor used to run callbacks that subclasses will implement. Any calls coming over Binder
47      * from {@link OverlayClient} should perform the work they need to do on this executor.
48      */
49     private Executor mExecutor;
50 
51     // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
52     // requests to the {@link DreamOverlayService}
53     private static class OverlayClient extends IDreamOverlayClient.Stub {
54         private final DreamOverlayService mService;
55         private boolean mShowComplications;
56         private ComponentName mDreamComponent;
57         IDreamOverlayCallback mDreamOverlayCallback;
58 
OverlayClient(DreamOverlayService service)59         OverlayClient(DreamOverlayService service) {
60             mService = service;
61         }
62 
63         @Override
startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback, String dreamComponent, boolean shouldShowComplications)64         public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
65                 String dreamComponent, boolean shouldShowComplications) throws RemoteException {
66             mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
67             mShowComplications = shouldShowComplications;
68             mDreamOverlayCallback = callback;
69             mService.startDream(this, params);
70         }
71 
72         @Override
wakeUp()73         public void wakeUp() {
74             mService.wakeUp(this);
75         }
76 
77         @Override
endDream()78         public void endDream() {
79             mService.endDream(this);
80         }
81 
onExitRequested()82         private void onExitRequested() {
83             try {
84                 mDreamOverlayCallback.onExitRequested();
85             } catch (RemoteException e) {
86                 Log.e(TAG, "Could not request exit:" + e);
87             }
88         }
89 
shouldShowComplications()90         private boolean shouldShowComplications() {
91             return mShowComplications;
92         }
93 
getComponent()94         private ComponentName getComponent() {
95             return mDreamComponent;
96         }
97     }
98 
startDream(OverlayClient client, WindowManager.LayoutParams params)99     private void startDream(OverlayClient client, WindowManager.LayoutParams params) {
100         // Run on executor as this is a binder call from OverlayClient.
101         mExecutor.execute(() -> {
102             endDreamInternal(mCurrentClient);
103             mCurrentClient = client;
104             onStartDream(params);
105         });
106     }
107 
endDream(OverlayClient client)108     private void endDream(OverlayClient client) {
109         // Run on executor as this is a binder call from OverlayClient.
110         mExecutor.execute(() -> endDreamInternal(client));
111     }
112 
endDreamInternal(OverlayClient client)113     private void endDreamInternal(OverlayClient client) {
114         if (client == null || client != mCurrentClient) {
115             return;
116         }
117 
118         onEndDream();
119         mCurrentClient = null;
120     }
121 
wakeUp(OverlayClient client)122     private void wakeUp(OverlayClient client) {
123         // Run on executor as this is a binder call from OverlayClient.
124         mExecutor.execute(() -> {
125             if (mCurrentClient != client) {
126                 return;
127             }
128 
129             onWakeUp();
130         });
131     }
132 
133     private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
134         @Override
135         public void getClient(IDreamOverlayClientCallback callback) {
136             try {
137                 callback.onDreamOverlayClient(
138                         new OverlayClient(DreamOverlayService.this));
139             } catch (RemoteException e) {
140                 Log.e(TAG, "could not send client to callback", e);
141             }
142         }
143     };
144 
DreamOverlayService()145     public DreamOverlayService() {
146     }
147 
148     /**
149      * This constructor allows providing an executor to run callbacks on.
150      *
151      * @hide
152      */
DreamOverlayService(@onNull Executor executor)153     public DreamOverlayService(@NonNull Executor executor) {
154         mExecutor = executor;
155     }
156 
157     @Override
onCreate()158     public void onCreate() {
159         super.onCreate();
160         if (mExecutor == null) {
161             // If no executor was provided, use the main executor. onCreate is the earliest time
162             // getMainExecutor is available.
163             mExecutor = getMainExecutor();
164         }
165     }
166 
167     @Nullable
168     @Override
onBind(@onNull Intent intent)169     public final IBinder onBind(@NonNull Intent intent) {
170         return mDreamOverlay.asBinder();
171     }
172 
173     /**
174      * This method is overridden by implementations to handle when the dream has started and the
175      * window is ready to be interacted with.
176      *
177      * This callback will be run on the {@link Executor} provided in the constructor if provided, or
178      * on the main executor if none was provided.
179      *
180      * @param layoutParams The {@link android.view.WindowManager.LayoutParams} associated with the
181      *                     dream window.
182      */
onStartDream(@onNull WindowManager.LayoutParams layoutParams)183     public abstract void onStartDream(@NonNull WindowManager.LayoutParams layoutParams);
184 
185     /**
186      * This method is overridden by implementations to handle when the dream has been requested
187      * to wakeup.
188      * @hide
189      */
onWakeUp()190     public void onWakeUp() {}
191 
192     /**
193      * This method is overridden by implementations to handle when the dream has ended. There may
194      * be earlier signals leading up to this step, such as @{@link #onWakeUp(Runnable)}.
195      *
196      * This callback will be run on the {@link Executor} provided in the constructor if provided, or
197      * on the main executor if none was provided.
198      */
onEndDream()199     public void onEndDream() {
200     }
201 
202     /**
203      * This method is invoked to request the dream exit.
204      */
requestExit()205     public final void requestExit() {
206         if (mCurrentClient == null) {
207             throw new IllegalStateException("requested exit with no dream present");
208         }
209 
210         mCurrentClient.onExitRequested();
211     }
212 
213     /**
214      * Returns whether to show complications on the dream overlay.
215      */
shouldShowComplications()216     public final boolean shouldShowComplications() {
217         if (mCurrentClient == null) {
218             throw new IllegalStateException(
219                     "requested if should show complication when no dream active");
220         }
221 
222         return mCurrentClient.shouldShowComplications();
223     }
224 
225     /**
226      * Returns the active dream component.
227      * @hide
228      */
getDreamComponent()229     public final ComponentName getDreamComponent() {
230         if (mCurrentClient == null) {
231             throw new IllegalStateException("requested dream component when no dream active");
232         }
233 
234         return mCurrentClient.getComponent();
235     }
236 }
237