Home | History | Annotate | Download | only in recents
      1 /*
      2  * Copyright (C) 2014 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.systemui.recents;
     18 
     19 import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
     20 import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
     21 
     22 import android.animation.ArgbEvaluator;
     23 import android.animation.ValueAnimator;
     24 import android.app.ActivityManager;
     25 import android.app.ActivityTaskManager;
     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.RemoteException;
     34 import android.util.DisplayMetrics;
     35 import android.view.Gravity;
     36 import android.view.View;
     37 import android.view.ViewGroup;
     38 import android.view.WindowManager;
     39 import android.view.accessibility.AccessibilityManager;
     40 import android.view.animation.DecelerateInterpolator;
     41 import android.widget.Button;
     42 import android.widget.FrameLayout;
     43 import android.widget.ImageView;
     44 import android.widget.LinearLayout;
     45 import android.widget.TextView;
     46 
     47 import com.android.systemui.Dependency;
     48 import com.android.systemui.R;
     49 import com.android.systemui.SysUiServiceProvider;
     50 import com.android.systemui.shared.system.QuickStepContract;
     51 import com.android.systemui.shared.system.WindowManagerWrapper;
     52 import com.android.systemui.statusbar.phone.NavigationBarView;
     53 import com.android.systemui.statusbar.phone.NavigationModeController;
     54 import com.android.systemui.statusbar.phone.StatusBar;
     55 import com.android.systemui.util.leak.RotationUtils;
     56 
     57 import java.util.ArrayList;
     58 
     59 public class ScreenPinningRequest implements View.OnClickListener,
     60         NavigationModeController.ModeChangedListener {
     61 
     62     private final Context mContext;
     63 
     64     private final AccessibilityManager mAccessibilityService;
     65     private final WindowManager mWindowManager;
     66     private final OverviewProxyService mOverviewProxyService;
     67 
     68     private RequestWindowView mRequestWindow;
     69     private int mNavBarMode;
     70 
     71     // Id of task to be pinned or locked.
     72     private int taskId;
     73 
     74     public ScreenPinningRequest(Context context) {
     75         mContext = context;
     76         mAccessibilityService = (AccessibilityManager)
     77                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
     78         mWindowManager = (WindowManager)
     79                 mContext.getSystemService(Context.WINDOW_SERVICE);
     80         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
     81         mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
     82     }
     83 
     84     public void clearPrompt() {
     85         if (mRequestWindow != null) {
     86             mWindowManager.removeView(mRequestWindow);
     87             mRequestWindow = null;
     88         }
     89     }
     90 
     91     public void showPrompt(int taskId, boolean allowCancel) {
     92         try {
     93             clearPrompt();
     94         } catch (IllegalArgumentException e) {
     95             // If the call to show the prompt fails due to the request window not already being
     96             // attached, then just ignore the error since we will be re-adding it below.
     97         }
     98 
     99         this.taskId = taskId;
    100 
    101         mRequestWindow = new RequestWindowView(mContext, allowCancel);
    102 
    103         mRequestWindow.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
    104 
    105         // show the confirmation
    106         WindowManager.LayoutParams lp = getWindowLayoutParams();
    107         mWindowManager.addView(mRequestWindow, lp);
    108     }
    109 
    110     @Override
    111     public void onNavigationModeChanged(int mode) {
    112         mNavBarMode = mode;
    113     }
    114 
    115     public void onConfigurationChanged() {
    116         if (mRequestWindow != null) {
    117             mRequestWindow.onConfigurationChanged();
    118         }
    119     }
    120 
    121     private WindowManager.LayoutParams getWindowLayoutParams() {
    122         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
    123                 ViewGroup.LayoutParams.MATCH_PARENT,
    124                 ViewGroup.LayoutParams.MATCH_PARENT,
    125                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
    126                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    127                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
    128                 PixelFormat.TRANSLUCENT);
    129         lp.token = new Binder();
    130         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
    131         lp.setTitle("ScreenPinningConfirmation");
    132         lp.gravity = Gravity.FILL;
    133         return lp;
    134     }
    135 
    136     @Override
    137     public void onClick(View v) {
    138         if (v.getId() == R.id.screen_pinning_ok_button || mRequestWindow == v) {
    139             try {
    140                 ActivityTaskManager.getService().startSystemLockTaskMode(taskId);
    141             } catch (RemoteException e) {}
    142         }
    143         clearPrompt();
    144     }
    145 
    146     public FrameLayout.LayoutParams getRequestLayoutParams(int rotation) {
    147         return new FrameLayout.LayoutParams(
    148                 ViewGroup.LayoutParams.WRAP_CONTENT,
    149                 ViewGroup.LayoutParams.WRAP_CONTENT,
    150                 rotation == ROTATION_SEASCAPE ? (Gravity.CENTER_VERTICAL | Gravity.LEFT) :
    151                 rotation == ROTATION_LANDSCAPE ? (Gravity.CENTER_VERTICAL | Gravity.RIGHT)
    152                             : (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM));
    153     }
    154 
    155     private class RequestWindowView extends FrameLayout {
    156         private static final int OFFSET_DP = 96;
    157 
    158         private final ColorDrawable mColor = new ColorDrawable(0);
    159         private ValueAnimator mColorAnim;
    160         private ViewGroup mLayout;
    161         private boolean mShowCancel;
    162 
    163         public RequestWindowView(Context context, boolean showCancel) {
    164             super(context);
    165             setClickable(true);
    166             setOnClickListener(ScreenPinningRequest.this);
    167             setBackground(mColor);
    168             mShowCancel = showCancel;
    169         }
    170 
    171         @Override
    172         public void onAttachedToWindow() {
    173             DisplayMetrics metrics = new DisplayMetrics();
    174             mWindowManager.getDefaultDisplay().getMetrics(metrics);
    175             float density = metrics.density;
    176             int rotation = RotationUtils.getRotation(mContext);
    177 
    178             inflateView(rotation);
    179             int bgColor = mContext.getColor(
    180                     R.color.screen_pinning_request_window_bg);
    181             if (ActivityManager.isHighEndGfx()) {
    182                 mLayout.setAlpha(0f);
    183                 if (rotation == ROTATION_SEASCAPE) {
    184                     mLayout.setTranslationX(-OFFSET_DP * density);
    185                 } else if (rotation == ROTATION_LANDSCAPE) {
    186                     mLayout.setTranslationX(OFFSET_DP * density);
    187                 } else {
    188                     mLayout.setTranslationY(OFFSET_DP * density);
    189                 }
    190                 mLayout.animate()
    191                         .alpha(1f)
    192                         .translationX(0)
    193                         .translationY(0)
    194                         .setDuration(300)
    195                         .setInterpolator(new DecelerateInterpolator())
    196                         .start();
    197 
    198                 mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, bgColor);
    199                 mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    200                     @Override
    201                     public void onAnimationUpdate(ValueAnimator animation) {
    202                         final int c = (Integer) animation.getAnimatedValue();
    203                         mColor.setColor(c);
    204                     }
    205                 });
    206                 mColorAnim.setDuration(1000);
    207                 mColorAnim.start();
    208             } else {
    209                 mColor.setColor(bgColor);
    210             }
    211 
    212             IntentFilter filter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
    213             filter.addAction(Intent.ACTION_USER_SWITCHED);
    214             filter.addAction(Intent.ACTION_SCREEN_OFF);
    215             mContext.registerReceiver(mReceiver, filter);
    216         }
    217 
    218         private void inflateView(int rotation) {
    219             // We only want this landscape orientation on <600dp, so rather than handle
    220             // resource overlay for -land and -sw600dp-land, just inflate this
    221             // other view for this single case.
    222             mLayout = (ViewGroup) View.inflate(getContext(),
    223                     rotation == ROTATION_SEASCAPE ? R.layout.screen_pinning_request_sea_phone :
    224                     rotation == ROTATION_LANDSCAPE ? R.layout.screen_pinning_request_land_phone
    225                             : R.layout.screen_pinning_request,
    226                     null);
    227             // Catch touches so they don't trigger cancel/activate, like outside does.
    228             mLayout.setClickable(true);
    229             // Status bar is always on the right.
    230             mLayout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
    231             // Buttons and text do switch sides though.
    232             mLayout.findViewById(R.id.screen_pinning_text_area)
    233                     .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
    234             View buttons = mLayout.findViewById(R.id.screen_pinning_buttons);
    235             WindowManagerWrapper wm = WindowManagerWrapper.getInstance();
    236             if (!QuickStepContract.isGesturalMode(mNavBarMode)
    237             	    && wm.hasSoftNavigationBar(mContext.getDisplayId())) {
    238                 buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
    239                 swapChildrenIfRtlAndVertical(buttons);
    240             } else {
    241                 buttons.setVisibility(View.GONE);
    242             }
    243 
    244             ((Button) mLayout.findViewById(R.id.screen_pinning_ok_button))
    245                     .setOnClickListener(ScreenPinningRequest.this);
    246             if (mShowCancel) {
    247                 ((Button) mLayout.findViewById(R.id.screen_pinning_cancel_button))
    248                         .setOnClickListener(ScreenPinningRequest.this);
    249             } else {
    250                 ((Button) mLayout.findViewById(R.id.screen_pinning_cancel_button))
    251                         .setVisibility(View.INVISIBLE);
    252             }
    253 
    254             StatusBar statusBar = SysUiServiceProvider.getComponent(mContext, StatusBar.class);
    255             NavigationBarView navigationBarView =
    256                     statusBar != null ? statusBar.getNavigationBarView() : null;
    257             final boolean recentsVisible = navigationBarView != null
    258                     && navigationBarView.isRecentsButtonVisible();
    259             boolean touchExplorationEnabled = mAccessibilityService.isTouchExplorationEnabled();
    260             int descriptionStringResId;
    261             if (QuickStepContract.isGesturalMode(mNavBarMode)) {
    262                 descriptionStringResId = R.string.screen_pinning_description_gestural;
    263             } else if (recentsVisible) {
    264                 mLayout.findViewById(R.id.screen_pinning_recents_group).setVisibility(VISIBLE);
    265                 mLayout.findViewById(R.id.screen_pinning_home_bg_light).setVisibility(INVISIBLE);
    266                 mLayout.findViewById(R.id.screen_pinning_home_bg).setVisibility(INVISIBLE);
    267                 descriptionStringResId = touchExplorationEnabled
    268                         ? R.string.screen_pinning_description_accessible
    269                         : R.string.screen_pinning_description;
    270             } else {
    271                 mLayout.findViewById(R.id.screen_pinning_recents_group).setVisibility(INVISIBLE);
    272                 mLayout.findViewById(R.id.screen_pinning_home_bg_light).setVisibility(VISIBLE);
    273                 mLayout.findViewById(R.id.screen_pinning_home_bg).setVisibility(VISIBLE);
    274                 descriptionStringResId = touchExplorationEnabled
    275                         ? R.string.screen_pinning_description_recents_invisible_accessible
    276                         : R.string.screen_pinning_description_recents_invisible;
    277             }
    278 
    279             if (navigationBarView != null) {
    280                 ((ImageView) mLayout.findViewById(R.id.screen_pinning_back_icon))
    281                         .setImageDrawable(navigationBarView.getBackDrawable());
    282                 ((ImageView) mLayout.findViewById(R.id.screen_pinning_home_icon))
    283                         .setImageDrawable(navigationBarView.getHomeDrawable());
    284             }
    285 
    286             ((TextView) mLayout.findViewById(R.id.screen_pinning_description))
    287                     .setText(descriptionStringResId);
    288             final int backBgVisibility = touchExplorationEnabled ? View.INVISIBLE : View.VISIBLE;
    289             mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility);
    290             mLayout.findViewById(R.id.screen_pinning_back_bg_light).setVisibility(backBgVisibility);
    291 
    292             addView(mLayout, getRequestLayoutParams(rotation));
    293         }
    294 
    295         private void swapChildrenIfRtlAndVertical(View group) {
    296             if (mContext.getResources().getConfiguration().getLayoutDirection()
    297                     != View.LAYOUT_DIRECTION_RTL) {
    298                 return;
    299             }
    300             LinearLayout linearLayout = (LinearLayout) group;
    301             if (linearLayout.getOrientation() == LinearLayout.VERTICAL) {
    302                 int childCount = linearLayout.getChildCount();
    303                 ArrayList<View> childList = new ArrayList<>(childCount);
    304                 for (int i = 0; i < childCount; i++) {
    305                     childList.add(linearLayout.getChildAt(i));
    306                 }
    307                 linearLayout.removeAllViews();
    308                 for (int i = childCount - 1; i >= 0; i--) {
    309                     linearLayout.addView(childList.get(i));
    310                 }
    311             }
    312         }
    313 
    314         @Override
    315         public void onDetachedFromWindow() {
    316             mContext.unregisterReceiver(mReceiver);
    317         }
    318 
    319         protected void onConfigurationChanged() {
    320             removeAllViews();
    321             inflateView(RotationUtils.getRotation(mContext));
    322         }
    323 
    324         private final Runnable mUpdateLayoutRunnable = new Runnable() {
    325             @Override
    326             public void run() {
    327                 if (mLayout != null && mLayout.getParent() != null) {
    328                     mLayout.setLayoutParams(getRequestLayoutParams(RotationUtils.getRotation(mContext)));
    329                 }
    330             }
    331         };
    332 
    333         private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    334             @Override
    335             public void onReceive(Context context, Intent intent) {
    336                 if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
    337                     post(mUpdateLayoutRunnable);
    338                 } else if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)
    339                         || intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
    340                     clearPrompt();
    341                 }
    342             }
    343         };
    344     }
    345 
    346 }
    347