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 android.annotation.NonNull; 19 import android.app.Presentation; 20 import android.content.Context; 21 import android.hardware.devicestate.DeviceState; 22 import android.hardware.devicestate.DeviceStateManager; 23 import android.hardware.display.DisplayManager; 24 import android.media.MediaRouter; 25 import android.media.MediaRouter.RouteInfo; 26 import android.os.Trace; 27 import android.text.TextUtils; 28 import android.util.Log; 29 import android.util.SparseArray; 30 import android.view.Display; 31 import android.view.DisplayAddress; 32 import android.view.DisplayInfo; 33 import android.view.View; 34 import android.view.WindowManager; 35 36 import androidx.annotation.Nullable; 37 38 import com.android.systemui.dagger.SysUISingleton; 39 import com.android.systemui.dagger.qualifiers.Main; 40 import com.android.systemui.dagger.qualifiers.UiBackground; 41 import com.android.systemui.navigationbar.NavigationBarController; 42 import com.android.systemui.navigationbar.NavigationBarView; 43 import com.android.systemui.settings.DisplayTracker; 44 import com.android.systemui.statusbar.policy.KeyguardStateController; 45 46 import dagger.Lazy; 47 48 import java.util.concurrent.Executor; 49 50 import javax.inject.Inject; 51 52 @SysUISingleton 53 public class KeyguardDisplayManager { 54 protected static final String TAG = "KeyguardDisplayManager"; 55 private static final boolean DEBUG = KeyguardConstants.DEBUG; 56 57 private MediaRouter mMediaRouter = null; 58 private final DisplayManager mDisplayService; 59 private final DisplayTracker mDisplayTracker; 60 private final Lazy<NavigationBarController> mNavigationBarControllerLazy; 61 private final ConnectedDisplayKeyguardPresentation.Factory 62 mConnectedDisplayKeyguardPresentationFactory; 63 private final Context mContext; 64 65 private boolean mShowing; 66 private final DisplayInfo mTmpDisplayInfo = new DisplayInfo(); 67 68 private final DeviceStateHelper mDeviceStateHelper; 69 private final KeyguardStateController mKeyguardStateController; 70 71 private final SparseArray<Presentation> mPresentations = new SparseArray<>(); 72 73 private final DisplayTracker.Callback mDisplayCallback = 74 new DisplayTracker.Callback() { 75 @Override 76 public void onDisplayAdded(int displayId) { 77 Trace.beginSection( 78 "KeyguardDisplayManager#onDisplayAdded(displayId=" + displayId + ")"); 79 final Display display = mDisplayService.getDisplay(displayId); 80 if (mShowing) { 81 updateNavigationBarVisibility(displayId, false /* navBarVisible */); 82 showPresentation(display); 83 } 84 Trace.endSection(); 85 } 86 87 @Override 88 public void onDisplayRemoved(int displayId) { 89 Trace.beginSection( 90 "KeyguardDisplayManager#onDisplayRemoved(displayId=" + displayId + ")"); 91 hidePresentation(displayId); 92 Trace.endSection(); 93 } 94 }; 95 96 @Inject KeyguardDisplayManager(Context context, Lazy<NavigationBarController> navigationBarControllerLazy, DisplayTracker displayTracker, @Main Executor mainExecutor, @UiBackground Executor uiBgExecutor, DeviceStateHelper deviceStateHelper, KeyguardStateController keyguardStateController, ConnectedDisplayKeyguardPresentation.Factory connectedDisplayKeyguardPresentationFactory)97 public KeyguardDisplayManager(Context context, 98 Lazy<NavigationBarController> navigationBarControllerLazy, 99 DisplayTracker displayTracker, 100 @Main Executor mainExecutor, 101 @UiBackground Executor uiBgExecutor, 102 DeviceStateHelper deviceStateHelper, 103 KeyguardStateController keyguardStateController, 104 ConnectedDisplayKeyguardPresentation.Factory 105 connectedDisplayKeyguardPresentationFactory) { 106 mContext = context; 107 mNavigationBarControllerLazy = navigationBarControllerLazy; 108 uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class)); 109 mDisplayService = mContext.getSystemService(DisplayManager.class); 110 mDisplayTracker = displayTracker; 111 mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor); 112 mDeviceStateHelper = deviceStateHelper; 113 mKeyguardStateController = keyguardStateController; 114 mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory; 115 } 116 isKeyguardShowable(Display display)117 private boolean isKeyguardShowable(Display display) { 118 if (display == null) { 119 if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null display"); 120 return false; 121 } 122 if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) { 123 if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display"); 124 return false; 125 } 126 display.getDisplayInfo(mTmpDisplayInfo); 127 if ((mTmpDisplayInfo.flags & Display.FLAG_PRIVATE) != 0) { 128 if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on a private display"); 129 return false; 130 } 131 if ((mTmpDisplayInfo.flags & Display.FLAG_ALWAYS_UNLOCKED) != 0) { 132 if (DEBUG) { 133 Log.i(TAG, "Do not show KeyguardPresentation on an unlocked display"); 134 } 135 return false; 136 } 137 if (mKeyguardStateController.isOccluded() 138 && mDeviceStateHelper.isConcurrentDisplayActive(display)) { 139 if (DEBUG) { 140 // When activities with FLAG_SHOW_WHEN_LOCKED are shown on top of Keyguard, the 141 // Keyguard state becomes "occluded". In this case, we should not show the 142 // KeyguardPresentation, since the activity is presenting content onto the 143 // non-default display. 144 Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent" 145 + " display is active"); 146 } 147 return false; 148 } 149 150 return true; 151 } 152 /** 153 * @param display The display to show the presentation on. 154 * @return {@code true} if a presentation was added. 155 * {@code false} if the presentation cannot be added on that display or the presentation 156 * was already there. 157 */ showPresentation(Display display)158 private boolean showPresentation(Display display) { 159 if (!isKeyguardShowable(display)) return false; 160 if (DEBUG) Log.i(TAG, "Keyguard enabled on display: " + display); 161 final int displayId = display.getDisplayId(); 162 Presentation presentation = mPresentations.get(displayId); 163 if (presentation == null) { 164 final Presentation newPresentation = createPresentation(display); 165 newPresentation.setOnDismissListener(dialog -> { 166 if (newPresentation.equals(mPresentations.get(displayId))) { 167 mPresentations.remove(displayId); 168 } 169 }); 170 presentation = newPresentation; 171 try { 172 presentation.show(); 173 } catch (WindowManager.InvalidDisplayException ex) { 174 Log.w(TAG, "Invalid display:", ex); 175 presentation = null; 176 } 177 if (presentation != null) { 178 mPresentations.append(displayId, presentation); 179 return true; 180 } 181 } 182 return false; 183 } 184 createPresentation(Display display)185 Presentation createPresentation(Display display) { 186 return mConnectedDisplayKeyguardPresentationFactory.create(display); 187 } 188 189 /** 190 * @param displayId The id of the display to hide the presentation off. 191 */ hidePresentation(int displayId)192 private void hidePresentation(int displayId) { 193 final Presentation presentation = mPresentations.get(displayId); 194 if (presentation != null) { 195 presentation.dismiss(); 196 mPresentations.remove(displayId); 197 } 198 } 199 show()200 public void show() { 201 if (!mShowing) { 202 if (DEBUG) Log.v(TAG, "show"); 203 if (mMediaRouter != null) { 204 mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, 205 mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); 206 } else { 207 Log.w(TAG, "MediaRouter not yet initialized"); 208 } 209 updateDisplays(true /* showing */); 210 } 211 mShowing = true; 212 } 213 hide()214 public void hide() { 215 if (mShowing) { 216 if (DEBUG) Log.v(TAG, "hide"); 217 if (mMediaRouter != null) { 218 mMediaRouter.removeCallback(mMediaRouterCallback); 219 } 220 updateDisplays(false /* showing */); 221 } 222 mShowing = false; 223 } 224 225 private final MediaRouter.SimpleCallback mMediaRouterCallback = 226 new MediaRouter.SimpleCallback() { 227 @Override 228 public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { 229 if (DEBUG) Log.d(TAG, "onRouteSelected: type=" + type + ", info=" + info); 230 updateDisplays(mShowing); 231 } 232 233 @Override 234 public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { 235 if (DEBUG) Log.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info); 236 updateDisplays(mShowing); 237 } 238 239 @Override 240 public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) { 241 if (DEBUG) Log.d(TAG, "onRoutePresentationDisplayChanged: info=" + info); 242 updateDisplays(mShowing); 243 } 244 }; 245 updateDisplays(boolean showing)246 protected boolean updateDisplays(boolean showing) { 247 boolean changed = false; 248 if (showing) { 249 final Display[] displays = mDisplayTracker.getAllDisplays(); 250 for (Display display : displays) { 251 int displayId = display.getDisplayId(); 252 updateNavigationBarVisibility(displayId, false /* navBarVisible */); 253 changed |= showPresentation(display); 254 } 255 } else { 256 changed = mPresentations.size() > 0; 257 for (int i = mPresentations.size() - 1; i >= 0; i--) { 258 int displayId = mPresentations.keyAt(i); 259 updateNavigationBarVisibility(displayId, true /* navBarVisible */); 260 mPresentations.valueAt(i).dismiss(); 261 } 262 mPresentations.clear(); 263 } 264 return changed; 265 } 266 267 // TODO(b/127878649): this logic is from 268 // {@link StatusBarKeyguardViewManager#updateNavigationBarVisibility}. Try to revisit a long 269 // term solution in R. updateNavigationBarVisibility(int displayId, boolean navBarVisible)270 private void updateNavigationBarVisibility(int displayId, boolean navBarVisible) { 271 // Leave this task to {@link StatusBarKeyguardViewManager} 272 if (displayId == mDisplayTracker.getDefaultDisplayId()) return; 273 274 NavigationBarView navBarView = mNavigationBarControllerLazy.get() 275 .getNavigationBarView(displayId); 276 // We may not have nav bar on a display. 277 if (navBarView == null) return; 278 279 if (navBarVisible) { 280 navBarView.getRootView().setVisibility(View.VISIBLE); 281 } else { 282 navBarView.getRootView().setVisibility(View.GONE); 283 } 284 285 } 286 287 /** 288 * Helper used to receive device state info from {@link DeviceStateManager}. 289 */ 290 static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback { 291 292 @Nullable 293 private final DisplayAddress.Physical mRearDisplayPhysicalAddress; 294 295 // TODO(b/271317597): These device states should be defined in DeviceStateManager 296 private final int mConcurrentState; 297 private boolean mIsInConcurrentDisplayState; 298 299 @Inject DeviceStateHelper(Context context, DeviceStateManager deviceStateManager, @Main Executor mainExecutor)300 DeviceStateHelper(Context context, 301 DeviceStateManager deviceStateManager, 302 @Main Executor mainExecutor) { 303 304 final String rearDisplayPhysicalAddress = context.getResources().getString( 305 com.android.internal.R.string.config_rearDisplayPhysicalAddress); 306 if (TextUtils.isEmpty(rearDisplayPhysicalAddress)) { 307 mRearDisplayPhysicalAddress = null; 308 } else { 309 mRearDisplayPhysicalAddress = DisplayAddress 310 .fromPhysicalDisplayId(Long.parseLong(rearDisplayPhysicalAddress)); 311 } 312 313 mConcurrentState = context.getResources().getInteger( 314 com.android.internal.R.integer.config_deviceStateConcurrentRearDisplay); 315 deviceStateManager.registerCallback(mainExecutor, this); 316 } 317 318 @Override onDeviceStateChanged(@onNull DeviceState state)319 public void onDeviceStateChanged(@NonNull DeviceState state) { 320 // When concurrent state ends, the display also turns off. This is enforced in various 321 // ExtensionRearDisplayPresentationTest CTS tests. So, we don't need to invoke 322 // hide() since that will happen through the onDisplayRemoved callback. 323 mIsInConcurrentDisplayState = state.getIdentifier() == mConcurrentState; 324 } 325 isConcurrentDisplayActive(Display display)326 boolean isConcurrentDisplayActive(Display display) { 327 return mIsInConcurrentDisplayState 328 && mRearDisplayPhysicalAddress != null 329 && mRearDisplayPhysicalAddress.equals(display.getAddress()); 330 } 331 } 332 } 333