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