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