• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 package com.android.keyguard;
17 
18 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
19 
20 import android.annotation.NonNull;
21 import android.app.Presentation;
22 import android.content.Context;
23 import android.hardware.devicestate.DeviceState;
24 import android.hardware.devicestate.DeviceStateManager;
25 import android.hardware.display.DisplayManager;
26 import android.media.MediaRouter;
27 import android.media.MediaRouter.RouteInfo;
28 import android.os.Trace;
29 import android.util.Log;
30 import android.util.SparseArray;
31 import android.view.Display;
32 import android.view.DisplayInfo;
33 import android.view.View;
34 import android.view.WindowManager;
35 
36 import androidx.annotation.Nullable;
37 
38 import com.android.systemui.dagger.SysUISingleton;
39 import com.android.systemui.dagger.qualifiers.Application;
40 import com.android.systemui.dagger.qualifiers.Main;
41 import com.android.systemui.dagger.qualifiers.UiBackground;
42 import com.android.systemui.navigationbar.NavigationBarController;
43 import com.android.systemui.navigationbar.views.NavigationBarView;
44 import com.android.systemui.settings.DisplayTracker;
45 import com.android.systemui.shade.ShadeDisplayAware;
46 import com.android.systemui.shade.data.repository.ShadeDisplaysRepository;
47 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
48 import com.android.systemui.statusbar.policy.KeyguardStateController;
49 
50 import dagger.Lazy;
51 
52 import kotlinx.coroutines.CoroutineScope;
53 
54 import java.util.concurrent.Executor;
55 
56 import javax.inject.Inject;
57 import javax.inject.Provider;
58 
59 /**
60  * Manages Keyguard Presentations for non-primary display(s).
61  */
62 @SysUISingleton
63 public class KeyguardDisplayManager {
64     protected static final String TAG = "KeyguardDisplayManager";
65     private static final boolean DEBUG = KeyguardConstants.DEBUG;
66 
67     private MediaRouter mMediaRouter = null;
68     private final DisplayManager mDisplayService;
69     private final DisplayTracker mDisplayTracker;
70     private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
71     private final Provider<ShadeDisplaysRepository> mShadePositionRepositoryProvider;
72     private final ConnectedDisplayKeyguardPresentation.Factory
73             mConnectedDisplayKeyguardPresentationFactory;
74     private final Context mContext;
75 
76     private boolean mShowing;
77     private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
78 
79     private final DeviceStateHelper mDeviceStateHelper;
80     private final KeyguardStateController mKeyguardStateController;
81 
82     private final SparseArray<Presentation> mPresentations = new SparseArray<>();
83 
84     private final DisplayTracker.Callback mDisplayCallback =
85             new DisplayTracker.Callback() {
86                 @Override
87                 public void onDisplayAdded(int displayId) {
88                     Trace.beginSection(
89                             "KeyguardDisplayManager#onDisplayAdded(displayId=" + displayId + ")");
90                     final Display display = mDisplayService.getDisplay(displayId);
91                     if (mShowing) {
92                         updateNavigationBarVisibility(displayId, false /* navBarVisible */);
93                         showPresentation(display);
94                     }
95                     Trace.endSection();
96                 }
97 
98                 @Override
99                 public void onDisplayRemoved(int displayId) {
100                     Trace.beginSection(
101                             "KeyguardDisplayManager#onDisplayRemoved(displayId=" + displayId + ")");
102                     hidePresentation(displayId);
103                     Trace.endSection();
104                 }
105             };
106 
107     @Inject
KeyguardDisplayManager( @hadeDisplayAware Context context, Lazy<NavigationBarController> navigationBarControllerLazy, DisplayTracker displayTracker, @Main Executor mainExecutor, @UiBackground Executor uiBgExecutor, DeviceStateHelper deviceStateHelper, KeyguardStateController keyguardStateController, ConnectedDisplayKeyguardPresentation.Factory connectedDisplayKeyguardPresentationFactory, Provider<ShadeDisplaysRepository> shadePositionRepositoryProvider, @Application CoroutineScope appScope)108     public KeyguardDisplayManager(
109             @ShadeDisplayAware Context context,
110             Lazy<NavigationBarController> navigationBarControllerLazy,
111             DisplayTracker displayTracker,
112             @Main Executor mainExecutor,
113             @UiBackground Executor uiBgExecutor,
114             DeviceStateHelper deviceStateHelper,
115             KeyguardStateController keyguardStateController,
116             ConnectedDisplayKeyguardPresentation.Factory
117                     connectedDisplayKeyguardPresentationFactory,
118             Provider<ShadeDisplaysRepository> shadePositionRepositoryProvider,
119             @Application CoroutineScope appScope) {
120         mContext = context;
121         mNavigationBarControllerLazy = navigationBarControllerLazy;
122         mShadePositionRepositoryProvider = shadePositionRepositoryProvider;
123         uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class));
124         mDisplayService = mContext.getSystemService(DisplayManager.class);
125         mDisplayTracker = displayTracker;
126         mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
127         mDeviceStateHelper = deviceStateHelper;
128         mKeyguardStateController = keyguardStateController;
129         mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory;
130         if (ShadeWindowGoesAround.isEnabled()) {
131             collectFlow(appScope, shadePositionRepositoryProvider.get().getDisplayId(),
132                     (id) -> onShadeWindowMovedToDisplayId(id));
133         }
134     }
135 
onShadeWindowMovedToDisplayId(int shadeDisplayId)136     private void onShadeWindowMovedToDisplayId(int shadeDisplayId) {
137         if (mShowing) {
138             hidePresentation(shadeDisplayId);
139             updateDisplays(/* showing= */ true);
140         }
141     }
142 
isKeyguardShowable(Display display)143     private boolean isKeyguardShowable(Display display) {
144         if (display == null) {
145             Log.i(TAG, "Cannot show Keyguard on null display");
146             return false;
147         }
148         if (ShadeWindowGoesAround.isEnabled()) {
149             int shadeDisplayId = mShadePositionRepositoryProvider.get().getDisplayId().getValue();
150             if (display.getDisplayId() == shadeDisplayId) {
151                 Log.i(TAG, "Do not show KeyguardPresentation on the shade window display");
152                 return false;
153             }
154         } else {
155             if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) {
156                 Log.i(TAG, "Do not show KeyguardPresentation on the default display");
157                 return false;
158             }
159         }
160         display.getDisplayInfo(mTmpDisplayInfo);
161         if ((mTmpDisplayInfo.flags & Display.FLAG_PRIVATE) != 0) {
162             Log.i(TAG, "Do not show KeyguardPresentation on a private display");
163             return false;
164         }
165         if ((mTmpDisplayInfo.flags & Display.FLAG_ALWAYS_UNLOCKED) != 0) {
166             Log.i(TAG, "Do not show KeyguardPresentation on an unlocked display");
167             return false;
168         }
169 
170         final boolean deviceStateOccludesKeyguard =
171                 mDeviceStateHelper.isConcurrentDisplayActive(display)
172                         || mDeviceStateHelper.isRearDisplayOuterDefaultActive(display);
173         if (mKeyguardStateController.isOccluded() && deviceStateOccludesKeyguard) {
174             // When activities with FLAG_SHOW_WHEN_LOCKED are shown on top of Keyguard, the Keyguard
175             // state becomes "occluded". In this case, we should not show the KeyguardPresentation,
176             // since the activity is presenting content onto the non-default display.
177             Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent or rear"
178                     + " display is active");
179             return false;
180         }
181 
182         return true;
183     }
184     /**
185      * @param display The display to show the presentation on.
186      * @return {@code true} if a presentation was added.
187      *         {@code false} if the presentation cannot be added on that display or the presentation
188      *         was already there.
189      */
showPresentation(Display display)190     private boolean showPresentation(Display display) {
191         if (!isKeyguardShowable(display)) return false;
192         Log.i(TAG, "Keyguard enabled on display: " + display);
193         final int displayId = display.getDisplayId();
194         Presentation presentation = mPresentations.get(displayId);
195         if (presentation == null) {
196             final Presentation newPresentation = createPresentation(display);
197             newPresentation.setOnDismissListener(dialog -> {
198                 if (newPresentation.equals(mPresentations.get(displayId))) {
199                     mPresentations.remove(displayId);
200                 }
201             });
202             presentation = newPresentation;
203             try {
204                 presentation.show();
205             } catch (WindowManager.InvalidDisplayException ex) {
206                 Log.w(TAG, "Invalid display:", ex);
207                 presentation = null;
208             }
209             if (presentation != null) {
210                 mPresentations.append(displayId, presentation);
211                 return true;
212             }
213         }
214         return false;
215     }
216 
createPresentation(Display display)217     Presentation createPresentation(Display display) {
218         return mConnectedDisplayKeyguardPresentationFactory.create(display);
219     }
220 
221     /**
222      * @param displayId The id of the display to hide the presentation off.
223      */
hidePresentation(int displayId)224     private void hidePresentation(int displayId) {
225         final Presentation presentation = mPresentations.get(displayId);
226         if (presentation != null) {
227             presentation.dismiss();
228             mPresentations.remove(displayId);
229         }
230     }
231 
show()232     public void show() {
233         if (!mShowing) {
234             Log.v(TAG, "show");
235             if (mMediaRouter != null) {
236                 mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
237                         mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
238             } else {
239                 Log.w(TAG, "MediaRouter not yet initialized");
240             }
241             updateDisplays(true /* showing */);
242         }
243         mShowing = true;
244     }
245 
hide()246     public void hide() {
247         if (mShowing) {
248             Log.v(TAG, "hide");
249             if (mMediaRouter != null) {
250                 mMediaRouter.removeCallback(mMediaRouterCallback);
251             }
252             updateDisplays(false /* showing */);
253         }
254         mShowing = false;
255     }
256 
257     private final MediaRouter.SimpleCallback mMediaRouterCallback =
258             new MediaRouter.SimpleCallback() {
259         @Override
260         public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
261             if (DEBUG) Log.d(TAG, "onRouteSelected: type=" + type + ", info=" + info);
262             updateDisplays(mShowing);
263         }
264 
265         @Override
266         public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
267             if (DEBUG) Log.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info);
268             updateDisplays(mShowing);
269         }
270 
271         @Override
272         public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {
273             if (DEBUG) Log.d(TAG, "onRoutePresentationDisplayChanged: info=" + info);
274             updateDisplays(mShowing);
275         }
276     };
277 
updateDisplays(boolean showing)278     protected boolean updateDisplays(boolean showing) {
279         boolean changed = false;
280         if (showing) {
281             final Display[] displays = mDisplayTracker.getAllDisplays();
282             for (Display display : displays) {
283                 int displayId = display.getDisplayId();
284                 updateNavigationBarVisibility(displayId, false /* navBarVisible */);
285                 changed |= showPresentation(display);
286             }
287         } else {
288             changed = mPresentations.size() > 0;
289             for (int i = mPresentations.size() - 1; i >= 0; i--) {
290                 int displayId = mPresentations.keyAt(i);
291                 updateNavigationBarVisibility(displayId, true /* navBarVisible */);
292                 mPresentations.valueAt(i).dismiss();
293             }
294             mPresentations.clear();
295         }
296         return changed;
297     }
298 
299     // TODO(b/127878649): this logic is from
300     //  {@link StatusBarKeyguardViewManager#updateNavigationBarVisibility}. Try to revisit a long
301     //  term solution in R.
updateNavigationBarVisibility(int displayId, boolean navBarVisible)302     private void updateNavigationBarVisibility(int displayId, boolean navBarVisible) {
303         // Leave this task to {@link StatusBarKeyguardViewManager}
304         if (displayId == mDisplayTracker.getDefaultDisplayId()) return;
305 
306         NavigationBarView navBarView = mNavigationBarControllerLazy.get()
307                 .getNavigationBarView(displayId);
308         // We may not have nav bar on a display.
309         if (navBarView == null) return;
310 
311         if (navBarVisible) {
312             navBarView.getRootView().setVisibility(View.VISIBLE);
313         } else {
314             navBarView.getRootView().setVisibility(View.GONE);
315         }
316 
317     }
318 
319     /**
320      * Helper used to receive device state info from {@link DeviceStateManager}.
321      */
322     public static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
323 
324         @Nullable
325         private DeviceState mDeviceState;
326 
327         @Inject
DeviceStateHelper( DeviceStateManager deviceStateManager, @Main Executor mainExecutor)328         DeviceStateHelper(
329                 DeviceStateManager deviceStateManager,
330                 @Main Executor mainExecutor) {
331             deviceStateManager.registerCallback(mainExecutor, this);
332         }
333 
334         @Override
onDeviceStateChanged(@onNull DeviceState state)335         public void onDeviceStateChanged(@NonNull DeviceState state) {
336             // When dual display or rear display mode ends, the display also turns off. This is
337             // enforced in various ExtensionRearDisplayPresentationTest CTS tests. So, we don't need
338             // to invoke hide() since that will happen through the onDisplayRemoved callback.
339             mDeviceState = state;
340         }
341 
342         /**
343          * @return true if the device is in Dual Display mode, and the specified display is the
344          * rear facing (outer) display.
345          */
isConcurrentDisplayActive(@onNull Display display)346         boolean isConcurrentDisplayActive(@NonNull Display display) {
347             return mDeviceState != null
348                     && mDeviceState.hasProperty(
349                             DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT)
350                     && (display.getFlags() & Display.FLAG_REAR) != 0;
351         }
352 
353         /**
354          * @return true if the device is the updated Rear Display mode, and the specified display is
355          * the inner display. See {@link DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT}.
356          * Note that in this state, the outer display is the default display, while the inner
357          * display is the "rear" display.
358          */
isRearDisplayOuterDefaultActive(@onNull Display display)359         boolean isRearDisplayOuterDefaultActive(@NonNull Display display) {
360             return mDeviceState != null
361                     && mDeviceState.hasProperty(
362                             DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)
363                     && (display.getFlags() & Display.FLAG_REAR) != 0;
364         }
365     }
366 }
367