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 Display display = mDisplayService.getDisplay(displayId);
76             if (display != null && mShowing) {
77                 final Presentation presentation = mPresentations.get(displayId);
78                 if (presentation != null && !presentation.getDisplay().equals(display)) {
79                     hidePresentation(displayId);
80                     showPresentation(display);
81                 }
82             }
83         }
84 
85         @Override
86         public void onDisplayRemoved(int displayId) {
87             hidePresentation(displayId);
88         }
89     };
90 
KeyguardDisplayManager(Context context, InjectionInflationController injectableInflater)91     public KeyguardDisplayManager(Context context,
92             InjectionInflationController injectableInflater) {
93         mContext = context;
94         mInjectableInflater = injectableInflater;
95         mMediaRouter = mContext.getSystemService(MediaRouter.class);
96         mDisplayService = mContext.getSystemService(DisplayManager.class);
97         mDisplayService.registerDisplayListener(mDisplayListener, null /* handler */);
98     }
99 
isKeyguardShowable(Display display)100     private boolean isKeyguardShowable(Display display) {
101         if (display == null) {
102             if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null display");
103             return false;
104         }
105         if (display.getDisplayId() == DEFAULT_DISPLAY) {
106             if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display");
107             return false;
108         }
109         display.getDisplayInfo(mTmpDisplayInfo);
110         if ((mTmpDisplayInfo.flags & Display.FLAG_PRIVATE) != 0) {
111             if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on a private display");
112             return false;
113         }
114         return true;
115     }
116     /**
117      * @param display The display to show the presentation on.
118      * @return {@code true} if a presentation was added.
119      *         {@code false} if the presentation cannot be added on that display or the presentation
120      *         was already there.
121      */
showPresentation(Display display)122     private boolean showPresentation(Display display) {
123         if (!isKeyguardShowable(display)) return false;
124         if (DEBUG) Log.i(TAG, "Keyguard enabled on display: " + display);
125         final int displayId = display.getDisplayId();
126         Presentation presentation = mPresentations.get(displayId);
127         if (presentation == null) {
128             presentation = new KeyguardPresentation(mContext, display, mInjectableInflater);
129             presentation.setOnDismissListener(dialog -> {
130                 if (null != mPresentations.get(displayId)) {
131                     mPresentations.remove(displayId);
132                 }
133             });
134             try {
135                 presentation.show();
136             } catch (WindowManager.InvalidDisplayException ex) {
137                 Log.w(TAG, "Invalid display:", ex);
138                 presentation = null;
139             }
140             if (presentation != null) {
141                 mPresentations.append(displayId, presentation);
142                 return true;
143             }
144         }
145         return false;
146     }
147 
148     /**
149      * @param displayId The id of the display to hide the presentation off.
150      */
hidePresentation(int displayId)151     private void hidePresentation(int displayId) {
152         final Presentation presentation = mPresentations.get(displayId);
153         if (presentation != null) {
154             presentation.dismiss();
155             mPresentations.remove(displayId);
156         }
157     }
158 
show()159     public void show() {
160         if (!mShowing) {
161             if (DEBUG) Log.v(TAG, "show");
162             mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
163                     mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
164             updateDisplays(true /* showing */);
165         }
166         mShowing = true;
167     }
168 
hide()169     public void hide() {
170         if (mShowing) {
171             if (DEBUG) Log.v(TAG, "hide");
172             mMediaRouter.removeCallback(mMediaRouterCallback);
173             updateDisplays(false /* showing */);
174         }
175         mShowing = false;
176     }
177 
178     private final MediaRouter.SimpleCallback mMediaRouterCallback =
179             new MediaRouter.SimpleCallback() {
180         @Override
181         public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
182             if (DEBUG) Log.d(TAG, "onRouteSelected: type=" + type + ", info=" + info);
183             updateDisplays(mShowing);
184         }
185 
186         @Override
187         public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
188             if (DEBUG) Log.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info);
189             updateDisplays(mShowing);
190         }
191 
192         @Override
193         public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {
194             if (DEBUG) Log.d(TAG, "onRoutePresentationDisplayChanged: info=" + info);
195             updateDisplays(mShowing);
196         }
197     };
198 
updateDisplays(boolean showing)199     protected boolean updateDisplays(boolean showing) {
200         boolean changed = false;
201         if (showing) {
202             final Display[] displays = mDisplayService.getDisplays();
203             for (Display display : displays) {
204                 int displayId = display.getDisplayId();
205                 updateNavigationBarVisibility(displayId, false /* navBarVisible */);
206                 changed |= showPresentation(display);
207             }
208         } else {
209             changed = mPresentations.size() > 0;
210             for (int i = mPresentations.size() - 1; i >= 0; i--) {
211                 int displayId = mPresentations.keyAt(i);
212                 updateNavigationBarVisibility(displayId, true /* navBarVisible */);
213                 mPresentations.valueAt(i).dismiss();
214             }
215             mPresentations.clear();
216         }
217         return changed;
218     }
219 
220     // TODO(b/127878649): this logic is from
221     //  {@link StatusBarKeyguardViewManager#updateNavigationBarVisibility}. Try to revisit a long
222     //  term solution in R.
updateNavigationBarVisibility(int displayId, boolean navBarVisible)223     private void updateNavigationBarVisibility(int displayId, boolean navBarVisible) {
224         // Leave this task to {@link StatusBarKeyguardViewManager}
225         if (displayId == DEFAULT_DISPLAY) return;
226 
227         NavigationBarView navBarView = mNavBarController.getNavigationBarView(displayId);
228         // We may not have nav bar on a display.
229         if (navBarView == null) return;
230 
231         if (navBarVisible) {
232             navBarView.getRootView().setVisibility(View.VISIBLE);
233         } else {
234             navBarView.getRootView().setVisibility(View.GONE);
235         }
236 
237     }
238 
239     @VisibleForTesting
240     static final class KeyguardPresentation extends Presentation {
241         private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
242         private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
243         private final InjectionInflationController mInjectableInflater;
244         private View mClock;
245         private int mUsableWidth;
246         private int mUsableHeight;
247         private int mMarginTop;
248         private int mMarginLeft;
249         Runnable mMoveTextRunnable = new Runnable() {
250             @Override
251             public void run() {
252                 int x = mMarginLeft + (int) (Math.random() * (mUsableWidth - mClock.getWidth()));
253                 int y = mMarginTop + (int) (Math.random() * (mUsableHeight - mClock.getHeight()));
254                 mClock.setTranslationX(x);
255                 mClock.setTranslationY(y);
256                 mClock.postDelayed(mMoveTextRunnable, MOVE_CLOCK_TIMEOUT);
257             }
258         };
259 
KeyguardPresentation(Context context, Display display, InjectionInflationController injectionInflater)260         KeyguardPresentation(Context context, Display display,
261                 InjectionInflationController injectionInflater) {
262             super(context, display, R.style.Theme_SystemUI_KeyguardPresentation);
263             mInjectableInflater = injectionInflater;
264             getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
265             setCancelable(false);
266         }
267 
268         @Override
onDetachedFromWindow()269         public void onDetachedFromWindow() {
270             mClock.removeCallbacks(mMoveTextRunnable);
271         }
272 
273         @Override
onCreate(Bundle savedInstanceState)274         protected void onCreate(Bundle savedInstanceState) {
275             super.onCreate(savedInstanceState);
276 
277             Point p = new Point();
278             getDisplay().getSize(p);
279             mUsableWidth = VIDEO_SAFE_REGION * p.x/100;
280             mUsableHeight = VIDEO_SAFE_REGION * p.y/100;
281             mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200;
282             mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200;
283 
284             LayoutInflater inflater = mInjectableInflater.injectable(
285                     LayoutInflater.from(getContext()));
286             setContentView(inflater.inflate(R.layout.keyguard_presentation, null));
287 
288             // Logic to make the lock screen fullscreen
289             getWindow().getDecorView().setSystemUiVisibility(
290                     View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
291                             | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
292                             | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
293             getWindow().setNavigationBarContrastEnforced(false);
294             getWindow().setNavigationBarColor(Color.TRANSPARENT);
295 
296             mClock = findViewById(R.id.clock);
297 
298             // Avoid screen burn in
299             mClock.post(mMoveTextRunnable);
300         }
301     }
302 }
303