Home | History | Annotate | Download | only in impl
      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.internal.policy.impl;
     18 
     19 import android.animation.ArgbEvaluator;
     20 import android.animation.ValueAnimator;
     21 import android.app.ActivityManager;
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.graphics.PixelFormat;
     27 import android.graphics.drawable.ColorDrawable;
     28 import android.os.Handler;
     29 import android.os.Message;
     30 import android.os.UserHandle;
     31 import android.provider.Settings;
     32 import android.text.TextUtils;
     33 import android.util.ArraySet;
     34 import android.util.DisplayMetrics;
     35 import android.util.Slog;
     36 import android.view.Gravity;
     37 import android.view.MotionEvent;
     38 import android.view.View;
     39 import android.view.ViewGroup;
     40 import android.view.WindowManager;
     41 import android.view.animation.Animation;
     42 import android.view.animation.AnimationUtils;
     43 import android.view.animation.DecelerateInterpolator;
     44 import android.widget.Button;
     45 import android.widget.FrameLayout;
     46 
     47 import com.android.internal.R;
     48 
     49 import java.util.Arrays;
     50 
     51 /**
     52  *  Helper to manage showing/hiding a confirmation prompt when the navigation bar is hidden
     53  *  entering immersive mode.
     54  */
     55 public class ImmersiveModeConfirmation {
     56     private static final String TAG = "ImmersiveModeConfirmation";
     57     private static final boolean DEBUG = false;
     58     private static final boolean DEBUG_SHOW_EVERY_TIME = false; // super annoying, use with caution
     59 
     60     private final Context mContext;
     61     private final H mHandler;
     62     private final ArraySet<String> mConfirmedPackages = new ArraySet<String>();
     63     private final long mShowDelayMs;
     64     private final long mPanicThresholdMs;
     65 
     66     private ClingWindowView mClingWindow;
     67     private String mLastPackage;
     68     private String mPromptPackage;
     69     private long mPanicTime;
     70     private String mPanicPackage;
     71     private WindowManager mWindowManager;
     72 
     73     public ImmersiveModeConfirmation(Context context) {
     74         mContext = context;
     75         mHandler = new H();
     76         mShowDelayMs = getNavBarExitDuration() * 3;
     77         mPanicThresholdMs = context.getResources()
     78                 .getInteger(R.integer.config_immersive_mode_confirmation_panic);
     79         mWindowManager = (WindowManager)
     80                 mContext.getSystemService(Context.WINDOW_SERVICE);
     81     }
     82 
     83     private long getNavBarExitDuration() {
     84         Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit);
     85         return exit != null ? exit.getDuration() : 0;
     86     }
     87 
     88     public void loadSetting() {
     89         if (DEBUG) Slog.d(TAG, "loadSetting()");
     90         mConfirmedPackages.clear();
     91         String packages = null;
     92         try {
     93             packages = Settings.Secure.getStringForUser(mContext.getContentResolver(),
     94                     Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
     95                     UserHandle.USER_CURRENT);
     96             if (packages != null) {
     97                 mConfirmedPackages.addAll(Arrays.asList(packages.split(",")));
     98                 if (DEBUG) Slog.d(TAG, "Loaded mConfirmedPackages=" + mConfirmedPackages);
     99             }
    100         } catch (Throwable t) {
    101             Slog.w(TAG, "Error loading confirmations, packages=" + packages, t);
    102         }
    103     }
    104 
    105     private void saveSetting() {
    106         if (DEBUG) Slog.d(TAG, "saveSetting()");
    107         try {
    108             final String packages = TextUtils.join(",", mConfirmedPackages);
    109             Settings.Secure.putStringForUser(mContext.getContentResolver(),
    110                     Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
    111                     packages,
    112                     UserHandle.USER_CURRENT);
    113             if (DEBUG) Slog.d(TAG, "Saved packages=" + packages);
    114         } catch (Throwable t) {
    115             Slog.w(TAG, "Error saving confirmations, mConfirmedPackages=" + mConfirmedPackages, t);
    116         }
    117     }
    118 
    119     public void immersiveModeChanged(String pkg, boolean isImmersiveMode) {
    120         if (pkg == null) {
    121             return;
    122         }
    123         mHandler.removeMessages(H.SHOW);
    124         if (isImmersiveMode) {
    125             mLastPackage = pkg;
    126             if (DEBUG_SHOW_EVERY_TIME || !mConfirmedPackages.contains(pkg)) {
    127                 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.SHOW, pkg), mShowDelayMs);
    128             }
    129         } else {
    130             mLastPackage = null;
    131             mHandler.sendEmptyMessage(H.HIDE);
    132         }
    133     }
    134 
    135     public void onPowerKeyDown(boolean isScreenOn, long time, boolean inImmersiveMode) {
    136         if (mPanicPackage != null && !isScreenOn && (time - mPanicTime < mPanicThresholdMs)) {
    137             // turning the screen back on within the panic threshold
    138             unconfirmPackage(mPanicPackage);
    139         }
    140         if (isScreenOn && inImmersiveMode) {
    141             // turning the screen off, remember if we were in immersive mode
    142             mPanicTime = time;
    143             mPanicPackage = mLastPackage;
    144         } else {
    145             mPanicTime = 0;
    146             mPanicPackage = null;
    147         }
    148     }
    149 
    150     public void confirmCurrentPrompt() {
    151         mHandler.post(confirmAction(mPromptPackage));
    152     }
    153 
    154     private void unconfirmPackage(String pkg) {
    155         if (pkg != null) {
    156             if (DEBUG) Slog.d(TAG, "Unconfirming immersive mode confirmation for " + pkg);
    157             mConfirmedPackages.remove(pkg);
    158             saveSetting();
    159         }
    160     }
    161 
    162     private void handleHide() {
    163         if (mClingWindow != null) {
    164             if (DEBUG) Slog.d(TAG, "Hiding immersive mode confirmation for " + mPromptPackage);
    165             mWindowManager.removeView(mClingWindow);
    166             mClingWindow = null;
    167         }
    168     }
    169 
    170     public WindowManager.LayoutParams getClingWindowLayoutParams() {
    171         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
    172                 ViewGroup.LayoutParams.MATCH_PARENT,
    173                 ViewGroup.LayoutParams.MATCH_PARENT,
    174                 WindowManager.LayoutParams.TYPE_TOAST,
    175                 0
    176                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    177                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    178                         | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
    179                 ,
    180                 PixelFormat.TRANSLUCENT);
    181         lp.setTitle("ImmersiveModeConfirmation");
    182         lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications;
    183         lp.gravity = Gravity.FILL;
    184         return lp;
    185     }
    186 
    187     public FrameLayout.LayoutParams getBubbleLayoutParams() {
    188         return new FrameLayout.LayoutParams(
    189                 mContext.getResources().getDimensionPixelSize(
    190                         R.dimen.immersive_mode_cling_width),
    191                 ViewGroup.LayoutParams.WRAP_CONTENT,
    192                 Gravity.CENTER_HORIZONTAL | Gravity.TOP);
    193     }
    194 
    195     private class ClingWindowView extends FrameLayout {
    196         private static final int BGCOLOR = 0x80000000;
    197         private static final int OFFSET_DP = 48;
    198 
    199         private final Runnable mConfirm;
    200         private final ColorDrawable mColor = new ColorDrawable(0);
    201         private ValueAnimator mColorAnim;
    202         private ViewGroup mClingLayout;
    203 
    204         private Runnable mUpdateLayoutRunnable = new Runnable() {
    205             @Override
    206             public void run() {
    207                 if (mClingLayout != null && mClingLayout.getParent() != null) {
    208                     mClingLayout.setLayoutParams(getBubbleLayoutParams());
    209                 }
    210             }
    211         };
    212 
    213         private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    214             @Override
    215             public void onReceive(Context context, Intent intent) {
    216                 if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
    217                     post(mUpdateLayoutRunnable);
    218                 }
    219             }
    220         };
    221 
    222         public ClingWindowView(Context context, Runnable confirm) {
    223             super(context);
    224             mConfirm = confirm;
    225             setClickable(true);
    226             setBackground(mColor);
    227         }
    228 
    229         @Override
    230         public void onAttachedToWindow() {
    231             super.onAttachedToWindow();
    232 
    233             DisplayMetrics metrics = new DisplayMetrics();
    234             mWindowManager.getDefaultDisplay().getMetrics(metrics);
    235             float density = metrics.density;
    236 
    237             // create the confirmation cling
    238             mClingLayout = (ViewGroup)
    239                     View.inflate(getContext(), R.layout.immersive_mode_cling, null);
    240 
    241             final Button ok = (Button) mClingLayout.findViewById(R.id.ok);
    242             ok.setOnClickListener(new OnClickListener() {
    243                 @Override
    244                 public void onClick(View v) {
    245                     mConfirm.run();
    246                 }
    247             });
    248             addView(mClingLayout, getBubbleLayoutParams());
    249 
    250             if (ActivityManager.isHighEndGfx()) {
    251                 final View bubble = mClingLayout.findViewById(R.id.text);
    252                 bubble.setAlpha(0f);
    253                 bubble.setTranslationY(-OFFSET_DP*density);
    254                 bubble.animate()
    255                         .alpha(1f)
    256                         .translationY(0)
    257                         .setDuration(300)
    258                         .setInterpolator(new DecelerateInterpolator())
    259                         .start();
    260 
    261                 ok.setAlpha(0f);
    262                 ok.setTranslationY(-OFFSET_DP*density);
    263                 ok.animate().alpha(1f)
    264                         .translationY(0)
    265                         .setDuration(300)
    266                         .setStartDelay(200)
    267                         .setInterpolator(new DecelerateInterpolator())
    268                         .start();
    269 
    270                 mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR);
    271                 mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    272                     @Override
    273                     public void onAnimationUpdate(ValueAnimator animation) {
    274                         final int c = (Integer) animation.getAnimatedValue();
    275                         mColor.setColor(c);
    276                     }
    277                 });
    278                 mColorAnim.setDuration(1000);
    279                 mColorAnim.start();
    280             } else {
    281                 mColor.setColor(BGCOLOR);
    282             }
    283 
    284             mContext.registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
    285         }
    286 
    287         @Override
    288         public void onDetachedFromWindow() {
    289             mContext.unregisterReceiver(mReceiver);
    290         }
    291 
    292         @Override
    293         public boolean onTouchEvent(MotionEvent motion) {
    294             Slog.v(TAG, "ClingWindowView.onTouchEvent");
    295             return true;
    296         }
    297     }
    298 
    299     private void handleShow(String pkg) {
    300         mPromptPackage = pkg;
    301         if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation for " + pkg);
    302 
    303         mClingWindow = new ClingWindowView(mContext, confirmAction(pkg));
    304 
    305         // we will be hiding the nav bar, so layout as if it's already hidden
    306         mClingWindow.setSystemUiVisibility(
    307                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
    308               | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
    309 
    310         // show the confirmation
    311         WindowManager.LayoutParams lp = getClingWindowLayoutParams();
    312         mWindowManager.addView(mClingWindow, lp);
    313     }
    314 
    315     private Runnable confirmAction(final String pkg) {
    316         return new Runnable() {
    317             @Override
    318             public void run() {
    319                 if (pkg != null && !mConfirmedPackages.contains(pkg)) {
    320                     if (DEBUG) Slog.d(TAG, "Confirming immersive mode for " + pkg);
    321                     mConfirmedPackages.add(pkg);
    322                     saveSetting();
    323                 }
    324                 handleHide();
    325             }
    326         };
    327     }
    328 
    329     private final class H extends Handler {
    330         private static final int SHOW = 0;
    331         private static final int HIDE = 1;
    332 
    333         @Override
    334         public void handleMessage(Message msg) {
    335             switch(msg.what) {
    336                 case SHOW:
    337                     handleShow((String)msg.obj);
    338                     break;
    339                 case HIDE:
    340                     handleHide();
    341                     break;
    342             }
    343         }
    344     }
    345 }
    346