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