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