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