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 android.view.Display.DEFAULT_DISPLAY;
19 import static android.view.Display.DEFAULT_DISPLAY_GROUP;
20 
21 import android.app.Presentation;
22 import android.content.Context;
23 import android.graphics.Color;
24 import android.graphics.Rect;
25 import android.hardware.display.DisplayManager;
26 import android.media.MediaRouter;
27 import android.media.MediaRouter.RouteInfo;
28 import android.os.Bundle;
29 import android.util.Log;
30 import android.util.SparseArray;
31 import android.view.Display;
32 import android.view.DisplayInfo;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.WindowManager;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
39 import com.android.systemui.R;
40 import com.android.systemui.dagger.qualifiers.UiBackground;
41 import com.android.systemui.navigationbar.NavigationBarController;
42 import com.android.systemui.navigationbar.NavigationBarView;
43 
44 import java.util.concurrent.Executor;
45 
46 import javax.inject.Inject;
47 
48 import dagger.Lazy;
49 
50 public class KeyguardDisplayManager {
51     protected static final String TAG = "KeyguardDisplayManager";
52     private static boolean DEBUG = KeyguardConstants.DEBUG;
53 
54     private MediaRouter mMediaRouter = null;
55     private final DisplayManager mDisplayService;
56     private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
57     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
58     private final Context mContext;
59 
60     private boolean mShowing;
61     private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
62 
63     private final SparseArray<Presentation> mPresentations = new SparseArray<>();
64 
65     private final DisplayManager.DisplayListener mDisplayListener =
66             new DisplayManager.DisplayListener() {
67 
68         @Override
69         public void onDisplayAdded(int displayId) {
70             final Display display = mDisplayService.getDisplay(displayId);
71             if (mShowing) {
72                 updateNavigationBarVisibility(displayId, false /* navBarVisible */);
73                 showPresentation(display);
74             }
75         }
76 
77         @Override
78         public void onDisplayChanged(int displayId) {
79 
80         }
81 
82         @Override
83         public void onDisplayRemoved(int displayId) {
84             hidePresentation(displayId);
85         }
86     };
87 
88     @Inject
KeyguardDisplayManager(Context context, Lazy<NavigationBarController> navigationBarControllerLazy, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, @UiBackground Executor uiBgExecutor)89     public KeyguardDisplayManager(Context context,
90             Lazy<NavigationBarController> navigationBarControllerLazy,
91             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
92             @UiBackground Executor uiBgExecutor) {
93         mContext = context;
94         mNavigationBarControllerLazy = navigationBarControllerLazy;
95         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
96         uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class));
97         mDisplayService = mContext.getSystemService(DisplayManager.class);
98         mDisplayService.registerDisplayListener(mDisplayListener, null /* handler */);
99     }
100 
isKeyguardShowable(Display display)101     private boolean isKeyguardShowable(Display display) {
102         if (display == null) {
103             if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null display");
104             return false;
105         }
106         if (display.getDisplayId() == DEFAULT_DISPLAY) {
107             if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display");
108             return false;
109         }
110         display.getDisplayInfo(mTmpDisplayInfo);
111         if ((mTmpDisplayInfo.flags & Display.FLAG_PRIVATE) != 0) {
112             if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on a private display");
113             return false;
114         }
115         if (mTmpDisplayInfo.displayGroupId != DEFAULT_DISPLAY_GROUP) {
116             if (DEBUG) {
117                 Log.i(TAG,
118                         "Do not show KeyguardPresentation on a non-default group display");
119             }
120             return false;
121         }
122 
123         return true;
124     }
125     /**
126      * @param display The display to show the presentation on.
127      * @return {@code true} if a presentation was added.
128      *         {@code false} if the presentation cannot be added on that display or the presentation
129      *         was already there.
130      */
showPresentation(Display display)131     private boolean showPresentation(Display display) {
132         if (!isKeyguardShowable(display)) return false;
133         if (DEBUG) Log.i(TAG, "Keyguard enabled on display: " + display);
134         final int displayId = display.getDisplayId();
135         Presentation presentation = mPresentations.get(displayId);
136         if (presentation == null) {
137             final Presentation newPresentation = createPresentation(display);
138             newPresentation.setOnDismissListener(dialog -> {
139                 if (newPresentation.equals(mPresentations.get(displayId))) {
140                     mPresentations.remove(displayId);
141                 }
142             });
143             presentation = newPresentation;
144             try {
145                 presentation.show();
146             } catch (WindowManager.InvalidDisplayException ex) {
147                 Log.w(TAG, "Invalid display:", ex);
148                 presentation = null;
149             }
150             if (presentation != null) {
151                 mPresentations.append(displayId, presentation);
152                 return true;
153             }
154         }
155         return false;
156     }
157 
createPresentation(Display display)158     KeyguardPresentation createPresentation(Display display) {
159         return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory);
160     }
161 
162     /**
163      * @param displayId The id of the display to hide the presentation off.
164      */
hidePresentation(int displayId)165     private void hidePresentation(int displayId) {
166         final Presentation presentation = mPresentations.get(displayId);
167         if (presentation != null) {
168             presentation.dismiss();
169             mPresentations.remove(displayId);
170         }
171     }
172 
show()173     public void show() {
174         if (!mShowing) {
175             if (DEBUG) Log.v(TAG, "show");
176             if (mMediaRouter != null) {
177                 mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
178                         mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
179             } else {
180                 Log.w(TAG, "MediaRouter not yet initialized");
181             }
182             updateDisplays(true /* showing */);
183         }
184         mShowing = true;
185     }
186 
hide()187     public void hide() {
188         if (mShowing) {
189             if (DEBUG) Log.v(TAG, "hide");
190             if (mMediaRouter != null) {
191                 mMediaRouter.removeCallback(mMediaRouterCallback);
192             }
193             updateDisplays(false /* showing */);
194         }
195         mShowing = false;
196     }
197 
198     private final MediaRouter.SimpleCallback mMediaRouterCallback =
199             new MediaRouter.SimpleCallback() {
200         @Override
201         public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
202             if (DEBUG) Log.d(TAG, "onRouteSelected: type=" + type + ", info=" + info);
203             updateDisplays(mShowing);
204         }
205 
206         @Override
207         public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
208             if (DEBUG) Log.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info);
209             updateDisplays(mShowing);
210         }
211 
212         @Override
213         public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {
214             if (DEBUG) Log.d(TAG, "onRoutePresentationDisplayChanged: info=" + info);
215             updateDisplays(mShowing);
216         }
217     };
218 
updateDisplays(boolean showing)219     protected boolean updateDisplays(boolean showing) {
220         boolean changed = false;
221         if (showing) {
222             final Display[] displays = mDisplayService.getDisplays();
223             for (Display display : displays) {
224                 int displayId = display.getDisplayId();
225                 updateNavigationBarVisibility(displayId, false /* navBarVisible */);
226                 changed |= showPresentation(display);
227             }
228         } else {
229             changed = mPresentations.size() > 0;
230             for (int i = mPresentations.size() - 1; i >= 0; i--) {
231                 int displayId = mPresentations.keyAt(i);
232                 updateNavigationBarVisibility(displayId, true /* navBarVisible */);
233                 mPresentations.valueAt(i).dismiss();
234             }
235             mPresentations.clear();
236         }
237         return changed;
238     }
239 
240     // TODO(b/127878649): this logic is from
241     //  {@link StatusBarKeyguardViewManager#updateNavigationBarVisibility}. Try to revisit a long
242     //  term solution in R.
updateNavigationBarVisibility(int displayId, boolean navBarVisible)243     private void updateNavigationBarVisibility(int displayId, boolean navBarVisible) {
244         // Leave this task to {@link StatusBarKeyguardViewManager}
245         if (displayId == DEFAULT_DISPLAY) return;
246 
247         NavigationBarView navBarView = mNavigationBarControllerLazy.get()
248                 .getNavigationBarView(displayId);
249         // We may not have nav bar on a display.
250         if (navBarView == null) return;
251 
252         if (navBarVisible) {
253             navBarView.getRootView().setVisibility(View.VISIBLE);
254         } else {
255             navBarView.getRootView().setVisibility(View.GONE);
256         }
257 
258     }
259 
260     @VisibleForTesting
261     static final class KeyguardPresentation extends Presentation {
262         private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
263         private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
264         private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
265         private final Context mContext;
266         private KeyguardClockSwitchController mKeyguardClockSwitchController;
267         private View mClock;
268         private int mUsableWidth;
269         private int mUsableHeight;
270         private int mMarginTop;
271         private int mMarginLeft;
272         Runnable mMoveTextRunnable = new Runnable() {
273             @Override
274             public void run() {
275                 int x = mMarginLeft + (int) (Math.random() * (mUsableWidth - mClock.getWidth()));
276                 int y = mMarginTop + (int) (Math.random() * (mUsableHeight - mClock.getHeight()));
277                 mClock.setTranslationX(x);
278                 mClock.setTranslationY(y);
279                 mClock.postDelayed(mMoveTextRunnable, MOVE_CLOCK_TIMEOUT);
280             }
281         };
282 
KeyguardPresentation(Context context, Display display, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory)283         KeyguardPresentation(Context context, Display display,
284                 KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) {
285             super(context, display, R.style.Theme_SystemUI_KeyguardPresentation,
286                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
287             mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
288             setCancelable(false);
289             mContext = context;
290         }
291 
292         @Override
cancel()293         public void cancel() {
294             // Do not allow anything to cancel KeyguardPresentation except KeyguardDisplayManager.
295         }
296 
297         @Override
onDetachedFromWindow()298         public void onDetachedFromWindow() {
299             mClock.removeCallbacks(mMoveTextRunnable);
300         }
301 
302         @Override
onDisplayChanged()303         public void onDisplayChanged() {
304             updateBounds();
305             getWindow().getDecorView().requestLayout();
306         }
307 
308         @Override
onCreate(Bundle savedInstanceState)309         protected void onCreate(Bundle savedInstanceState) {
310             super.onCreate(savedInstanceState);
311 
312             updateBounds();
313 
314             setContentView(LayoutInflater.from(mContext)
315                     .inflate(R.layout.keyguard_presentation, null));
316 
317             // Logic to make the lock screen fullscreen
318             getWindow().getDecorView().setSystemUiVisibility(
319                     View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
320                             | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
321                             | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
322             getWindow().getAttributes().setFitInsetsTypes(0 /* types */);
323             getWindow().setNavigationBarContrastEnforced(false);
324             getWindow().setNavigationBarColor(Color.TRANSPARENT);
325 
326             mClock = findViewById(R.id.clock);
327 
328             // Avoid screen burn in
329             mClock.post(mMoveTextRunnable);
330 
331             mKeyguardClockSwitchController = mKeyguardStatusViewComponentFactory
332                     .build(findViewById(R.id.clock))
333                     .getKeyguardClockSwitchController();
334 
335             mKeyguardClockSwitchController.setOnlyClock(true);
336             mKeyguardClockSwitchController.init();
337         }
338 
updateBounds()339         private void updateBounds() {
340             final Rect bounds = getWindow().getWindowManager().getMaximumWindowMetrics()
341                     .getBounds();
342             mUsableWidth = VIDEO_SAFE_REGION * bounds.width() / 100;
343             mUsableHeight = VIDEO_SAFE_REGION * bounds.height() / 100;
344             mMarginLeft = (100 - VIDEO_SAFE_REGION) * bounds.width() / 200;
345             mMarginTop = (100 - VIDEO_SAFE_REGION) * bounds.height() / 200;
346         }
347     }
348 }
349