Home | History | Annotate | Download | only in keyguard
      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 
     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 
     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      */
    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      */
    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 
    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 
    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 
    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.
    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 
    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
    268         public void onDetachedFromWindow() {
    269             mClock.removeCallbacks(mMoveTextRunnable);
    270         }
    271 
    272         @Override
    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