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