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