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