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