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