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