• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.systemui.dreams;
18 
19 import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_WINDOW_TITLE;
20 import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_MANAGER;
21 
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.graphics.drawable.ColorDrawable;
25 import android.util.Log;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.view.Window;
29 import android.view.WindowInsets;
30 import android.view.WindowManager;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 import androidx.annotation.VisibleForTesting;
35 import androidx.lifecycle.Lifecycle;
36 import androidx.lifecycle.LifecycleRegistry;
37 import androidx.lifecycle.ViewModelStore;
38 
39 import com.android.dream.lowlight.dagger.LowLightDreamModule;
40 import com.android.internal.logging.UiEvent;
41 import com.android.internal.logging.UiEventLogger;
42 import com.android.internal.policy.PhoneWindow;
43 import com.android.keyguard.KeyguardUpdateMonitor;
44 import com.android.keyguard.KeyguardUpdateMonitorCallback;
45 import com.android.systemui.complication.Complication;
46 import com.android.systemui.complication.dagger.ComplicationComponent;
47 import com.android.systemui.dagger.qualifiers.Main;
48 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
49 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
50 import com.android.systemui.touch.TouchInsetManager;
51 import com.android.systemui.util.concurrency.DelayableExecutor;
52 
53 import java.util.Arrays;
54 import java.util.HashSet;
55 
56 import javax.inject.Inject;
57 import javax.inject.Named;
58 
59 /**
60  * The {@link DreamOverlayService} is responsible for placing an overlay on top of a dream. The
61  * dream reaches directly out to the service with a Window reference (via LayoutParams), which the
62  * service uses to insert its own child Window into the dream's parent Window.
63  */
64 public class DreamOverlayService extends android.service.dreams.DreamOverlayService {
65     private static final String TAG = "DreamOverlayService";
66     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
67 
68     // The Context is used to construct the hosting constraint layout and child overlay views.
69     private final Context mContext;
70     // The Executor ensures actions and ui updates happen on the same thread.
71     private final DelayableExecutor mExecutor;
72     // A controller for the dream overlay container view (which contains both the status bar and the
73     // content area).
74     private DreamOverlayContainerViewController mDreamOverlayContainerViewController;
75     private final DreamOverlayCallbackController mDreamOverlayCallbackController;
76     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
77     @Nullable
78     private final ComponentName mLowLightDreamComponent;
79     private final UiEventLogger mUiEventLogger;
80     private final WindowManager mWindowManager;
81     private final String mWindowTitle;
82 
83     // A reference to the {@link Window} used to hold the dream overlay.
84     private Window mWindow;
85 
86     // True if a dream has bound to the service and dream overlay service has started.
87     private boolean mStarted = false;
88 
89     // True if the service has been destroyed.
90     private boolean mDestroyed = false;
91 
92     private final ComplicationComponent mComplicationComponent;
93 
94     private final com.android.systemui.dreams.complication.dagger.ComplicationComponent
95             mDreamComplicationComponent;
96 
97     private final DreamOverlayComponent mDreamOverlayComponent;
98 
99     private final DreamOverlayLifecycleOwner mLifecycleOwner;
100     private final LifecycleRegistry mLifecycleRegistry;
101 
102     private DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
103 
104     private final KeyguardUpdateMonitorCallback mKeyguardCallback =
105             new KeyguardUpdateMonitorCallback() {
106                 @Override
107                 public void onShadeExpandedChanged(boolean expanded) {
108                     mExecutor.execute(() -> {
109                         if (getCurrentStateLocked() != Lifecycle.State.RESUMED
110                                 && getCurrentStateLocked() != Lifecycle.State.STARTED) {
111                             return;
112                         }
113 
114                         setCurrentStateLocked(
115                                 expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED);
116                     });
117                 }
118             };
119 
120     private final DreamOverlayStateController.Callback mExitAnimationFinishedCallback =
121             new DreamOverlayStateController.Callback() {
122                 @Override
123                 public void onStateChanged() {
124                     if (!mStateController.areExitAnimationsRunning()) {
125                         mStateController.removeCallback(mExitAnimationFinishedCallback);
126                         resetCurrentDreamOverlayLocked();
127                     }
128                 }
129             };
130 
131     private final DreamOverlayStateController mStateController;
132 
133     @VisibleForTesting
134     public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
135         @UiEvent(doc = "The dream overlay has entered start.")
136         DREAM_OVERLAY_ENTER_START(989),
137         @UiEvent(doc = "The dream overlay has completed start.")
138         DREAM_OVERLAY_COMPLETE_START(990);
139 
140         private final int mId;
141 
DreamOverlayEvent(int id)142         DreamOverlayEvent(int id) {
143             mId = id;
144         }
145 
146         @Override
getId()147         public int getId() {
148             return mId;
149         }
150     }
151 
152     @Inject
DreamOverlayService( Context context, DreamOverlayLifecycleOwner lifecycleOwner, @Main DelayableExecutor executor, WindowManager windowManager, ComplicationComponent.Factory complicationComponentFactory, com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory dreamComplicationComponentFactory, DreamOverlayComponent.Factory dreamOverlayComponentFactory, DreamOverlayStateController stateController, KeyguardUpdateMonitor keyguardUpdateMonitor, UiEventLogger uiEventLogger, @Named(DREAM_TOUCH_INSET_MANAGER) TouchInsetManager touchInsetManager, @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT) ComponentName lowLightDreamComponent, DreamOverlayCallbackController dreamOverlayCallbackController, @Named(DREAM_OVERLAY_WINDOW_TITLE) String windowTitle)153     public DreamOverlayService(
154             Context context,
155             DreamOverlayLifecycleOwner lifecycleOwner,
156             @Main DelayableExecutor executor,
157             WindowManager windowManager,
158             ComplicationComponent.Factory complicationComponentFactory,
159             com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
160                     dreamComplicationComponentFactory,
161             DreamOverlayComponent.Factory dreamOverlayComponentFactory,
162             DreamOverlayStateController stateController,
163             KeyguardUpdateMonitor keyguardUpdateMonitor,
164             UiEventLogger uiEventLogger,
165             @Named(DREAM_TOUCH_INSET_MANAGER) TouchInsetManager touchInsetManager,
166             @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
167                     ComponentName lowLightDreamComponent,
168             DreamOverlayCallbackController dreamOverlayCallbackController,
169             @Named(DREAM_OVERLAY_WINDOW_TITLE) String windowTitle) {
170         super(executor);
171         mContext = context;
172         mExecutor = executor;
173         mWindowManager = windowManager;
174         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
175         mLowLightDreamComponent = lowLightDreamComponent;
176         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
177         mStateController = stateController;
178         mUiEventLogger = uiEventLogger;
179         mDreamOverlayCallbackController = dreamOverlayCallbackController;
180         mWindowTitle = windowTitle;
181 
182         final ViewModelStore viewModelStore = new ViewModelStore();
183         final Complication.Host host =
184                 () -> mExecutor.execute(DreamOverlayService.this::requestExit);
185 
186         mComplicationComponent = complicationComponentFactory.create(lifecycleOwner, host,
187                 viewModelStore, touchInsetManager);
188         mDreamComplicationComponent = dreamComplicationComponentFactory.create(
189                 mComplicationComponent.getVisibilityController(), touchInsetManager);
190         mDreamOverlayComponent = dreamOverlayComponentFactory.create(lifecycleOwner,
191                 mComplicationComponent.getComplicationHostViewController(), touchInsetManager,
192                 new HashSet<>(Arrays.asList(
193                         mDreamComplicationComponent.getHideComplicationTouchHandler())));
194         mLifecycleOwner = lifecycleOwner;
195         mLifecycleRegistry = mLifecycleOwner.getRegistry();
196 
197         mExecutor.execute(() -> setCurrentStateLocked(Lifecycle.State.CREATED));
198     }
199 
200     @Override
onDestroy()201     public void onDestroy() {
202         mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
203 
204         mExecutor.execute(() -> {
205             setCurrentStateLocked(Lifecycle.State.DESTROYED);
206 
207             resetCurrentDreamOverlayLocked();
208 
209             mDestroyed = true;
210         });
211 
212         super.onDestroy();
213     }
214 
215     @Override
onStartDream(@onNull WindowManager.LayoutParams layoutParams)216     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
217         setCurrentStateLocked(Lifecycle.State.STARTED);
218 
219         mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
220 
221         if (mDestroyed) {
222             // The task could still be executed after the service has been destroyed. Bail if
223             // that is the case.
224             return;
225         }
226 
227         if (mStarted) {
228             // Reset the current dream overlay before starting a new one. This can happen
229             // when two dreams overlap (briefly, for a smoother dream transition) and both
230             // dreams are bound to the dream overlay service.
231             resetCurrentDreamOverlayLocked();
232         }
233 
234         mDreamOverlayContainerViewController =
235                 mDreamOverlayComponent.getDreamOverlayContainerViewController();
236         mDreamOverlayTouchMonitor = mDreamOverlayComponent.getDreamOverlayTouchMonitor();
237         mDreamOverlayTouchMonitor.init();
238 
239         mStateController.setShouldShowComplications(shouldShowComplications());
240 
241         // If we are not able to add the overlay window, reset the overlay.
242         if (!addOverlayWindowLocked(layoutParams)) {
243             resetCurrentDreamOverlayLocked();
244             return;
245         }
246 
247         setCurrentStateLocked(Lifecycle.State.RESUMED);
248         mStateController.setOverlayActive(true);
249         final ComponentName dreamComponent = getDreamComponent();
250         mStateController.setLowLightActive(
251                 dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
252         mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
253 
254         mDreamOverlayCallbackController.onStartDream();
255         mStarted = true;
256     }
257 
258     @Override
onEndDream()259     public void onEndDream() {
260         resetCurrentDreamOverlayLocked();
261     }
262 
getCurrentStateLocked()263     private Lifecycle.State getCurrentStateLocked() {
264         return mLifecycleRegistry.getCurrentState();
265     }
266 
setCurrentStateLocked(Lifecycle.State state)267     private void setCurrentStateLocked(Lifecycle.State state) {
268         mLifecycleRegistry.setCurrentState(state);
269     }
270 
271     @Override
onWakeUp()272     public void onWakeUp() {
273         if (mDreamOverlayContainerViewController != null) {
274             mDreamOverlayCallbackController.onWakeUp();
275             mDreamOverlayContainerViewController.wakeUp();
276         }
277     }
278 
279     /**
280      * Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
281      * called from the main executing thread. The window attributes closely mirror those that are
282      * set by the {@link android.service.dreams.DreamService} on the dream Window.
283      *
284      * @param layoutParams The {@link android.view.WindowManager.LayoutParams} which allow inserting
285      *                     into the dream window.
286      */
addOverlayWindowLocked(WindowManager.LayoutParams layoutParams)287     private boolean addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
288         mWindow = new PhoneWindow(mContext);
289         // Default to SystemUI name for TalkBack.
290         mWindow.setTitle(mWindowTitle);
291         mWindow.setAttributes(layoutParams);
292         mWindow.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
293 
294         mWindow.setBackgroundDrawable(new ColorDrawable(0));
295 
296         mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
297         mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
298         mWindow.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
299         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
300         // Hide all insets when the dream is showing
301         mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
302         mWindow.setDecorFitsSystemWindows(false);
303 
304         if (DEBUG) {
305             Log.d(TAG, "adding overlay window to dream");
306         }
307 
308         mDreamOverlayContainerViewController.init();
309         // Make extra sure the container view has been removed from its old parent (otherwise we
310         // risk an IllegalStateException in some cases when setting the container view as the
311         // window's content view and the container view hasn't been properly removed previously).
312         removeContainerViewFromParentLocked();
313 
314         mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
315 
316         // It is possible that a dream's window (and the dream as a whole) is no longer valid by
317         // the time the overlay service processes the dream. This can happen for example if
318         // another dream is started immediately after the existing dream begins. In this case, the
319         // overlay service should identify the situation through the thrown exception and tear down
320         // the overlay.
321         try {
322             mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
323             return true;
324         } catch (WindowManager.BadTokenException exception) {
325             Log.e(TAG, "Dream activity window invalid: " + layoutParams.packageName,
326                     exception);
327             return false;
328         }
329     }
330 
removeContainerViewFromParentLocked()331     private void removeContainerViewFromParentLocked() {
332         View containerView = mDreamOverlayContainerViewController.getContainerView();
333         if (containerView == null) {
334             return;
335         }
336         ViewGroup parentView = (ViewGroup) containerView.getParent();
337         if (parentView == null) {
338             return;
339         }
340         Log.w(TAG, "Removing dream overlay container view parent!");
341         parentView.removeView(containerView);
342     }
343 
resetCurrentDreamOverlayLocked()344     private void resetCurrentDreamOverlayLocked() {
345         if (mStateController.areExitAnimationsRunning()) {
346             mStateController.addCallback(mExitAnimationFinishedCallback);
347             return;
348         }
349 
350         if (mStarted && mWindow != null) {
351             try {
352                 mWindowManager.removeView(mWindow.getDecorView());
353             } catch (IllegalArgumentException e) {
354                 Log.e(TAG, "Error removing decor view when resetting overlay", e);
355             }
356         }
357 
358         mStateController.setOverlayActive(false);
359         mStateController.setLowLightActive(false);
360         mStateController.setEntryAnimationsFinished(false);
361 
362         mDreamOverlayContainerViewController = null;
363         mDreamOverlayTouchMonitor = null;
364 
365         mWindow = null;
366         mStarted = false;
367     }
368 }
369