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