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 ScreenPinningRequest(Context context)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 clearPrompt()84 public void clearPrompt() { 85 if (mRequestWindow != null) { 86 mWindowManager.removeView(mRequestWindow); 87 mRequestWindow = null; 88 } 89 } 90 showPrompt(int taskId, boolean allowCancel)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 onNavigationModeChanged(int mode)111 public void onNavigationModeChanged(int mode) { 112 mNavBarMode = mode; 113 } 114 onConfigurationChanged()115 public void onConfigurationChanged() { 116 if (mRequestWindow != null) { 117 mRequestWindow.onConfigurationChanged(); 118 } 119 } 120 getWindowLayoutParams()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 onClick(View v)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 getRequestLayoutParams(int rotation)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 RequestWindowView(Context context, boolean showCancel)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 onAttachedToWindow()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 inflateView(int rotation)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 swapChildrenIfRtlAndVertical(View group)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 onDetachedFromWindow()315 public void onDetachedFromWindow() { 316 mContext.unregisterReceiver(mReceiver); 317 } 318 onConfigurationChanged()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