Home | History | Annotate | Download | only in policy
      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 
     17 package com.android.server.policy;
     18 
     19 import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
     20 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
     21 
     22 import android.animation.ArgbEvaluator;
     23 import android.animation.ValueAnimator;
     24 import android.app.ActivityManager;
     25 import android.app.ActivityThread;
     26 import android.content.BroadcastReceiver;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.IntentFilter;
     30 import android.graphics.PixelFormat;
     31 import android.graphics.drawable.ColorDrawable;
     32 import android.os.Binder;
     33 import android.os.Handler;
     34 import android.os.IBinder;
     35 import android.os.Message;
     36 import android.os.RemoteException;
     37 import android.os.ServiceManager;
     38 import android.os.UserHandle;
     39 import android.os.UserManager;
     40 import android.provider.Settings;
     41 import android.service.vr.IVrManager;
     42 import android.service.vr.IVrStateCallbacks;
     43 import android.util.DisplayMetrics;
     44 import android.util.Slog;
     45 import android.view.Gravity;
     46 import android.view.MotionEvent;
     47 import android.view.View;
     48 import android.view.ViewGroup;
     49 import android.view.ViewTreeObserver;
     50 import android.view.WindowManager;
     51 import android.view.animation.Animation;
     52 import android.view.animation.AnimationUtils;
     53 import android.view.animation.Interpolator;
     54 import android.widget.Button;
     55 import android.widget.FrameLayout;
     56 
     57 import com.android.internal.R;
     58 import com.android.server.vr.VrManagerService;
     59 
     60 /**
     61  *  Helper to manage showing/hiding a confirmation prompt when the navigation bar is hidden
     62  *  entering immersive mode.
     63  */
     64 public class ImmersiveModeConfirmation {
     65     private static final String TAG = "ImmersiveModeConfirmation";
     66     private static final boolean DEBUG = false;
     67     private static final boolean DEBUG_SHOW_EVERY_TIME = false; // super annoying, use with caution
     68     private static final String CONFIRMED = "confirmed";
     69 
     70     private final Context mContext;
     71     private final H mHandler;
     72     private final long mShowDelayMs;
     73     private final long mPanicThresholdMs;
     74     private final IBinder mWindowToken = new Binder();
     75 
     76     private boolean mConfirmed;
     77     private ClingWindowView mClingWindow;
     78     private long mPanicTime;
     79     private WindowManager mWindowManager;
     80     private int mCurrentUserId;
     81     // Local copy of vr mode enabled state, to avoid calling into VrManager with
     82     // the lock held.
     83     boolean mVrModeEnabled = false;
     84     private int mLockTaskState = LOCK_TASK_MODE_NONE;
     85 
     86     public ImmersiveModeConfirmation(Context context) {
     87         mContext = ActivityThread.currentActivityThread().getSystemUiContext();
     88         mHandler = new H();
     89         mShowDelayMs = getNavBarExitDuration() * 3;
     90         mPanicThresholdMs = context.getResources()
     91                 .getInteger(R.integer.config_immersive_mode_confirmation_panic);
     92         mWindowManager = (WindowManager)
     93                 mContext.getSystemService(Context.WINDOW_SERVICE);
     94     }
     95 
     96     private long getNavBarExitDuration() {
     97         Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit);
     98         return exit != null ? exit.getDuration() : 0;
     99     }
    100 
    101     public void loadSetting(int currentUserId) {
    102         mConfirmed = false;
    103         mCurrentUserId = currentUserId;
    104         if (DEBUG) Slog.d(TAG, String.format("loadSetting() mCurrentUserId=%d", mCurrentUserId));
    105         String value = null;
    106         try {
    107             value = Settings.Secure.getStringForUser(mContext.getContentResolver(),
    108                     Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
    109                     UserHandle.USER_CURRENT);
    110             mConfirmed = CONFIRMED.equals(value);
    111             if (DEBUG) Slog.d(TAG, "Loaded mConfirmed=" + mConfirmed);
    112         } catch (Throwable t) {
    113             Slog.w(TAG, "Error loading confirmations, value=" + value, t);
    114         }
    115     }
    116 
    117     private void saveSetting() {
    118         if (DEBUG) Slog.d(TAG, "saveSetting()");
    119         try {
    120             final String value = mConfirmed ? CONFIRMED : null;
    121             Settings.Secure.putStringForUser(mContext.getContentResolver(),
    122                     Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
    123                     value,
    124                     UserHandle.USER_CURRENT);
    125             if (DEBUG) Slog.d(TAG, "Saved value=" + value);
    126         } catch (Throwable t) {
    127             Slog.w(TAG, "Error saving confirmations, mConfirmed=" + mConfirmed, t);
    128         }
    129     }
    130 
    131     void systemReady() {
    132         IVrManager vrManager = IVrManager.Stub.asInterface(
    133                 ServiceManager.getService(Context.VR_SERVICE));
    134         if (vrManager != null) {
    135             try {
    136                 vrManager.registerListener(mVrStateCallbacks);
    137                 mVrModeEnabled = vrManager.getVrModeState();
    138             } catch (RemoteException re) {
    139             }
    140         }
    141     }
    142 
    143     public void immersiveModeChangedLw(String pkg, boolean isImmersiveMode,
    144             boolean userSetupComplete, boolean navBarEmpty) {
    145         mHandler.removeMessages(H.SHOW);
    146         if (isImmersiveMode) {
    147             final boolean disabled = PolicyControl.disableImmersiveConfirmation(pkg);
    148             if (DEBUG) Slog.d(TAG, String.format("immersiveModeChanged() disabled=%s mConfirmed=%s",
    149                     disabled, mConfirmed));
    150             if (!disabled
    151                     && (DEBUG_SHOW_EVERY_TIME || !mConfirmed)
    152                     && userSetupComplete
    153                     && !mVrModeEnabled
    154                     && !navBarEmpty
    155                     && !UserManager.isDeviceInDemoMode(mContext)
    156                     && (mLockTaskState != LOCK_TASK_MODE_LOCKED)) {
    157                 mHandler.sendEmptyMessageDelayed(H.SHOW, mShowDelayMs);
    158             }
    159         } else {
    160             mHandler.sendEmptyMessage(H.HIDE);
    161         }
    162     }
    163 
    164     public boolean onPowerKeyDown(boolean isScreenOn, long time, boolean inImmersiveMode,
    165             boolean navBarEmpty) {
    166         if (!isScreenOn && (time - mPanicTime < mPanicThresholdMs)) {
    167             // turning the screen back on within the panic threshold
    168             return mClingWindow == null;
    169         }
    170         if (isScreenOn && inImmersiveMode && !navBarEmpty) {
    171             // turning the screen off, remember if we were in immersive mode
    172             mPanicTime = time;
    173         } else {
    174             mPanicTime = 0;
    175         }
    176         return false;
    177     }
    178 
    179     public void confirmCurrentPrompt() {
    180         if (mClingWindow != null) {
    181             if (DEBUG) Slog.d(TAG, "confirmCurrentPrompt()");
    182             mHandler.post(mConfirm);
    183         }
    184     }
    185 
    186     private void handleHide() {
    187         if (mClingWindow != null) {
    188             if (DEBUG) Slog.d(TAG, "Hiding immersive mode confirmation");
    189             mWindowManager.removeView(mClingWindow);
    190             mClingWindow = null;
    191         }
    192     }
    193 
    194     public WindowManager.LayoutParams getClingWindowLayoutParams() {
    195         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
    196                 ViewGroup.LayoutParams.MATCH_PARENT,
    197                 ViewGroup.LayoutParams.MATCH_PARENT,
    198                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
    199                 0
    200                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    201                         | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
    202                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
    203                 ,
    204                 PixelFormat.TRANSLUCENT);
    205         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
    206         lp.setTitle("ImmersiveModeConfirmation");
    207         lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
    208         lp.token = getWindowToken();
    209         return lp;
    210     }
    211 
    212     public FrameLayout.LayoutParams getBubbleLayoutParams() {
    213         return new FrameLayout.LayoutParams(
    214                 mContext.getResources().getDimensionPixelSize(
    215                         R.dimen.immersive_mode_cling_width),
    216                 ViewGroup.LayoutParams.WRAP_CONTENT,
    217                 Gravity.CENTER_HORIZONTAL | Gravity.TOP);
    218     }
    219 
    220     /**
    221      * @return the window token that's used by all ImmersiveModeConfirmation windows.
    222      */
    223     public IBinder getWindowToken() {
    224         return mWindowToken;
    225     }
    226 
    227     private class ClingWindowView extends FrameLayout {
    228         private static final int BGCOLOR = 0x80000000;
    229         private static final int OFFSET_DP = 96;
    230         private static final int ANIMATION_DURATION = 250;
    231 
    232         private final Runnable mConfirm;
    233         private final ColorDrawable mColor = new ColorDrawable(0);
    234         private final Interpolator mInterpolator;
    235         private ValueAnimator mColorAnim;
    236         private ViewGroup mClingLayout;
    237 
    238         private Runnable mUpdateLayoutRunnable = new Runnable() {
    239             @Override
    240             public void run() {
    241                 if (mClingLayout != null && mClingLayout.getParent() != null) {
    242                     mClingLayout.setLayoutParams(getBubbleLayoutParams());
    243                 }
    244             }
    245         };
    246 
    247         private ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener =
    248                 new ViewTreeObserver.OnComputeInternalInsetsListener() {
    249                     private final int[] mTmpInt2 = new int[2];
    250 
    251                     @Override
    252                     public void onComputeInternalInsets(
    253                             ViewTreeObserver.InternalInsetsInfo inoutInfo) {
    254                         // Set touchable region to cover the cling layout.
    255                         mClingLayout.getLocationInWindow(mTmpInt2);
    256                         inoutInfo.setTouchableInsets(
    257                                 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
    258                         inoutInfo.touchableRegion.set(
    259                                 mTmpInt2[0],
    260                                 mTmpInt2[1],
    261                                 mTmpInt2[0] + mClingLayout.getWidth(),
    262                                 mTmpInt2[1] + mClingLayout.getHeight());
    263                     }
    264                 };
    265 
    266         private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    267             @Override
    268             public void onReceive(Context context, Intent intent) {
    269                 if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
    270                     post(mUpdateLayoutRunnable);
    271                 }
    272             }
    273         };
    274 
    275         public ClingWindowView(Context context, Runnable confirm) {
    276             super(context);
    277             mConfirm = confirm;
    278             setBackground(mColor);
    279             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
    280             mInterpolator = AnimationUtils
    281                     .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in);
    282         }
    283 
    284         @Override
    285         public void onAttachedToWindow() {
    286             super.onAttachedToWindow();
    287 
    288             DisplayMetrics metrics = new DisplayMetrics();
    289             mWindowManager.getDefaultDisplay().getMetrics(metrics);
    290             float density = metrics.density;
    291 
    292             getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
    293 
    294             // create the confirmation cling
    295             mClingLayout = (ViewGroup)
    296                     View.inflate(getContext(), R.layout.immersive_mode_cling, null);
    297 
    298             final Button ok = (Button) mClingLayout.findViewById(R.id.ok);
    299             ok.setOnClickListener(new OnClickListener() {
    300                 @Override
    301                 public void onClick(View v) {
    302                     mConfirm.run();
    303                 }
    304             });
    305             addView(mClingLayout, getBubbleLayoutParams());
    306 
    307             if (ActivityManager.isHighEndGfx()) {
    308                 final View cling = mClingLayout;
    309                 cling.setAlpha(0f);
    310                 cling.setTranslationY(-OFFSET_DP * density);
    311 
    312                 postOnAnimation(new Runnable() {
    313                     @Override
    314                     public void run() {
    315                         cling.animate()
    316                                 .alpha(1f)
    317                                 .translationY(0)
    318                                 .setDuration(ANIMATION_DURATION)
    319                                 .setInterpolator(mInterpolator)
    320                                 .withLayer()
    321                                 .start();
    322 
    323                         mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR);
    324                         mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    325                             @Override
    326                             public void onAnimationUpdate(ValueAnimator animation) {
    327                                 final int c = (Integer) animation.getAnimatedValue();
    328                                 mColor.setColor(c);
    329                             }
    330                         });
    331                         mColorAnim.setDuration(ANIMATION_DURATION);
    332                         mColorAnim.setInterpolator(mInterpolator);
    333                         mColorAnim.start();
    334                     }
    335                 });
    336             } else {
    337                 mColor.setColor(BGCOLOR);
    338             }
    339 
    340             mContext.registerReceiver(mReceiver,
    341                     new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
    342         }
    343 
    344         @Override
    345         public void onDetachedFromWindow() {
    346             mContext.unregisterReceiver(mReceiver);
    347         }
    348 
    349         @Override
    350         public boolean onTouchEvent(MotionEvent motion) {
    351             return true;
    352         }
    353     }
    354 
    355     private void handleShow() {
    356         if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation");
    357 
    358         mClingWindow = new ClingWindowView(mContext, mConfirm);
    359 
    360         // we will be hiding the nav bar, so layout as if it's already hidden
    361         mClingWindow.setSystemUiVisibility(
    362                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
    363               | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
    364 
    365         // show the confirmation
    366         WindowManager.LayoutParams lp = getClingWindowLayoutParams();
    367         mWindowManager.addView(mClingWindow, lp);
    368     }
    369 
    370     private final Runnable mConfirm = new Runnable() {
    371         @Override
    372         public void run() {
    373             if (DEBUG) Slog.d(TAG, "mConfirm.run()");
    374             if (!mConfirmed) {
    375                 mConfirmed = true;
    376                 saveSetting();
    377             }
    378             handleHide();
    379         }
    380     };
    381 
    382     private final class H extends Handler {
    383         private static final int SHOW = 1;
    384         private static final int HIDE = 2;
    385 
    386         @Override
    387         public void handleMessage(Message msg) {
    388             switch(msg.what) {
    389                 case SHOW:
    390                     handleShow();
    391                     break;
    392                 case HIDE:
    393                     handleHide();
    394                     break;
    395             }
    396         }
    397     }
    398 
    399     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
    400         @Override
    401         public void onVrStateChanged(boolean enabled) throws RemoteException {
    402             mVrModeEnabled = enabled;
    403             if (mVrModeEnabled) {
    404                 mHandler.removeMessages(H.SHOW);
    405                 mHandler.sendEmptyMessage(H.HIDE);
    406             }
    407         }
    408     };
    409 
    410     void onLockTaskModeChangedLw(int lockTaskState) {
    411         mLockTaskState = lockTaskState;
    412     }
    413 }
    414