• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
47 import java.util.ArrayList;
48 
49 public class ScreenPinningRequest implements View.OnClickListener {
50 
51     private static final int ROTATION_NONE = 0;
52     private static final int ROTATION_LANDSCAPE = 1;
53     private static final int ROTATION_SEASCAPE = 2;
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                 0
111                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
112                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
113                         | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
114                 ,
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
onClick(View v)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 
getRequestLayoutParams(int rotation)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 
RequestWindowView(Context context, boolean showCancel)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
onAttachedToWindow()159         public void onAttachedToWindow() {
160             DisplayMetrics metrics = new DisplayMetrics();
161             mWindowManager.getDefaultDisplay().getMetrics(metrics);
162             float density = metrics.density;
163             int rotation = 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 
getRotation(Context context)205         private int getRotation(Context context) {
206             Configuration config = mContext.getResources().getConfiguration();
207             int rot = context.getDisplay().getRotation();
208             if (config.smallestScreenWidthDp < 600) {
209                 if (rot == Surface.ROTATION_90) {
210                     return ROTATION_LANDSCAPE;
211                 } else if (rot == Surface.ROTATION_270) {
212                     return ROTATION_SEASCAPE;
213                 }
214             }
215             return ROTATION_NONE;
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             if (Recents.getSystemServices().hasSoftNavigationBar()) {
236                 buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
237                 swapChildrenIfRtlAndVertical(buttons);
238             } else {
239                 buttons.setVisibility(View.GONE);
240             }
241 
242             ((Button) mLayout.findViewById(R.id.screen_pinning_ok_button))
243                     .setOnClickListener(ScreenPinningRequest.this);
244             if (mShowCancel) {
245                 ((Button) mLayout.findViewById(R.id.screen_pinning_cancel_button))
246                         .setOnClickListener(ScreenPinningRequest.this);
247             } else {
248                 ((Button) mLayout.findViewById(R.id.screen_pinning_cancel_button))
249                         .setVisibility(View.INVISIBLE);
250             }
251 
252             boolean touchExplorationEnabled = mAccessibilityService.isTouchExplorationEnabled();
253             ((TextView) mLayout.findViewById(R.id.screen_pinning_description))
254                     .setText(touchExplorationEnabled
255                             ? R.string.screen_pinning_description_accessible
256                             : R.string.screen_pinning_description);
257             final int backBgVisibility = touchExplorationEnabled ? View.INVISIBLE : View.VISIBLE;
258             mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility);
259             mLayout.findViewById(R.id.screen_pinning_back_bg_light).setVisibility(backBgVisibility);
260 
261             addView(mLayout, getRequestLayoutParams(rotation));
262         }
263 
swapChildrenIfRtlAndVertical(View group)264         private void swapChildrenIfRtlAndVertical(View group) {
265             if (mContext.getResources().getConfiguration().getLayoutDirection()
266                     != View.LAYOUT_DIRECTION_RTL) {
267                 return;
268             }
269             LinearLayout linearLayout = (LinearLayout) group;
270             if (linearLayout.getOrientation() == LinearLayout.VERTICAL) {
271                 int childCount = linearLayout.getChildCount();
272                 ArrayList<View> childList = new ArrayList<>(childCount);
273                 for (int i = 0; i < childCount; i++) {
274                     childList.add(linearLayout.getChildAt(i));
275                 }
276                 linearLayout.removeAllViews();
277                 for (int i = childCount - 1; i >= 0; i--) {
278                     linearLayout.addView(childList.get(i));
279                 }
280             }
281         }
282 
283         @Override
onDetachedFromWindow()284         public void onDetachedFromWindow() {
285             mContext.unregisterReceiver(mReceiver);
286         }
287 
onConfigurationChanged()288         protected void onConfigurationChanged() {
289             removeAllViews();
290             inflateView(getRotation(mContext));
291         }
292 
293         private final Runnable mUpdateLayoutRunnable = new Runnable() {
294             @Override
295             public void run() {
296                 if (mLayout != null && mLayout.getParent() != null) {
297                     mLayout.setLayoutParams(getRequestLayoutParams(getRotation(mContext)));
298                 }
299             }
300         };
301 
302         private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
303             @Override
304             public void onReceive(Context context, Intent intent) {
305                 if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
306                     post(mUpdateLayoutRunnable);
307                 } else if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)
308                         || intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
309                     clearPrompt();
310                 }
311             }
312         };
313     }
314 
315 }
316