• 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 static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
20 import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
21 import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
22 import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
23 
24 import android.animation.ArgbEvaluator;
25 import android.animation.ValueAnimator;
26 import android.app.ActivityManager;
27 import android.app.ActivityTaskManager;
28 import android.content.BroadcastReceiver;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.res.Configuration;
33 import android.graphics.PixelFormat;
34 import android.graphics.drawable.ColorDrawable;
35 import android.os.Binder;
36 import android.os.RemoteException;
37 import android.text.SpannableStringBuilder;
38 import android.text.style.BulletSpan;
39 import android.util.DisplayMetrics;
40 import android.util.Log;
41 import android.view.Gravity;
42 import android.view.View;
43 import android.view.ViewGroup;
44 import android.view.WindowManager;
45 import android.view.WindowManagerGlobal;
46 import android.view.accessibility.AccessibilityManager;
47 import android.view.animation.DecelerateInterpolator;
48 import android.widget.Button;
49 import android.widget.FrameLayout;
50 import android.widget.ImageView;
51 import android.widget.LinearLayout;
52 import android.widget.TextView;
53 
54 import androidx.annotation.NonNull;
55 
56 import com.android.systemui.R;
57 import com.android.systemui.broadcast.BroadcastDispatcher;
58 import com.android.systemui.navigationbar.NavigationBarView;
59 import com.android.systemui.navigationbar.NavigationModeController;
60 import com.android.systemui.settings.UserTracker;
61 import com.android.systemui.shared.system.QuickStepContract;
62 import com.android.systemui.statusbar.phone.CentralSurfaces;
63 import com.android.systemui.util.leak.RotationUtils;
64 
65 import java.util.ArrayList;
66 import java.util.Optional;
67 
68 import javax.inject.Inject;
69 
70 import dagger.Lazy;
71 
72 public class ScreenPinningRequest implements View.OnClickListener,
73         NavigationModeController.ModeChangedListener {
74     private static final String TAG = "ScreenPinningRequest";
75 
76     private final Context mContext;
77     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
78 
79     private final AccessibilityManager mAccessibilityService;
80     private final WindowManager mWindowManager;
81     private final BroadcastDispatcher mBroadcastDispatcher;
82     private final UserTracker mUserTracker;
83 
84     private RequestWindowView mRequestWindow;
85     private int mNavBarMode;
86 
87     /** ID of task to be pinned or locked. */
88     private int taskId;
89 
90     private final UserTracker.Callback mUserChangedCallback =
91             new UserTracker.Callback() {
92                 @Override
93                 public void onUserChanged(int newUser, @NonNull Context userContext) {
94                     clearPrompt();
95                 }
96             };
97 
98     @Inject
ScreenPinningRequest( Context context, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, NavigationModeController navigationModeController, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker)99     public ScreenPinningRequest(
100             Context context,
101             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
102             NavigationModeController navigationModeController,
103             BroadcastDispatcher broadcastDispatcher,
104             UserTracker userTracker) {
105         mContext = context;
106         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
107         mAccessibilityService = (AccessibilityManager)
108                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
109         mWindowManager = (WindowManager)
110                 mContext.getSystemService(Context.WINDOW_SERVICE);
111         mNavBarMode = navigationModeController.addListener(this);
112         mBroadcastDispatcher = broadcastDispatcher;
113         mUserTracker = userTracker;
114     }
115 
clearPrompt()116     public void clearPrompt() {
117         if (mRequestWindow != null) {
118             mWindowManager.removeView(mRequestWindow);
119             mRequestWindow = null;
120         }
121     }
122 
showPrompt(int taskId, boolean allowCancel)123     public void showPrompt(int taskId, boolean allowCancel) {
124         try {
125             clearPrompt();
126         } catch (IllegalArgumentException e) {
127             // If the call to show the prompt fails due to the request window not already being
128             // attached, then just ignore the error since we will be re-adding it below.
129         }
130 
131         this.taskId = taskId;
132 
133         mRequestWindow = new RequestWindowView(mContext, allowCancel);
134 
135         mRequestWindow.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
136 
137         // show the confirmation
138         WindowManager.LayoutParams lp = getWindowLayoutParams();
139         mWindowManager.addView(mRequestWindow, lp);
140     }
141 
142     @Override
onNavigationModeChanged(int mode)143     public void onNavigationModeChanged(int mode) {
144         mNavBarMode = mode;
145     }
146 
onConfigurationChanged()147     public void onConfigurationChanged() {
148         if (mRequestWindow != null) {
149             mRequestWindow.onConfigurationChanged();
150         }
151     }
152 
getWindowLayoutParams()153     protected WindowManager.LayoutParams getWindowLayoutParams() {
154         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
155                 ViewGroup.LayoutParams.MATCH_PARENT,
156                 ViewGroup.LayoutParams.MATCH_PARENT,
157                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
158                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
159                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
160                 PixelFormat.TRANSLUCENT);
161         lp.token = new Binder();
162         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
163         lp.setTitle("ScreenPinningConfirmation");
164         lp.gravity = Gravity.FILL;
165         lp.setFitInsetsTypes(0 /* types */);
166         return lp;
167     }
168 
169     @Override
onClick(View v)170     public void onClick(View v) {
171         if (v.getId() == R.id.screen_pinning_ok_button || mRequestWindow == v) {
172             try {
173                 ActivityTaskManager.getService().startSystemLockTaskMode(taskId);
174             } catch (RemoteException e) {}
175         }
176         clearPrompt();
177     }
178 
getRequestLayoutParams(int rotation)179     public FrameLayout.LayoutParams getRequestLayoutParams(int rotation) {
180         return new FrameLayout.LayoutParams(
181                 ViewGroup.LayoutParams.WRAP_CONTENT,
182                 ViewGroup.LayoutParams.WRAP_CONTENT,
183                 rotation == ROTATION_SEASCAPE ? (Gravity.CENTER_VERTICAL | Gravity.LEFT) :
184                 rotation == ROTATION_LANDSCAPE ? (Gravity.CENTER_VERTICAL | Gravity.RIGHT)
185                             : (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM));
186     }
187 
188     private class RequestWindowView extends FrameLayout {
189         private static final int OFFSET_DP = 96;
190 
191         private final ColorDrawable mColor = new ColorDrawable(0);
192         private ViewGroup mLayout;
193         private final boolean mShowCancel;
194 
RequestWindowView(Context context, boolean showCancel)195         private RequestWindowView(Context context, boolean showCancel) {
196             super(context);
197             setClickable(true);
198             setOnClickListener(ScreenPinningRequest.this);
199             setBackground(mColor);
200             mShowCancel = showCancel;
201         }
202 
203         @Override
onAttachedToWindow()204         public void onAttachedToWindow() {
205             DisplayMetrics metrics = new DisplayMetrics();
206             mWindowManager.getDefaultDisplay().getMetrics(metrics);
207             float density = metrics.density;
208             int rotation = getRotation(mContext);
209 
210             inflateView(rotation);
211             int bgColor = mContext.getColor(
212                     R.color.screen_pinning_request_window_bg);
213             if (ActivityManager.isHighEndGfx()) {
214                 mLayout.setAlpha(0f);
215                 if (rotation == ROTATION_SEASCAPE) {
216                     mLayout.setTranslationX(-OFFSET_DP * density);
217                 } else if (rotation == ROTATION_LANDSCAPE) {
218                     mLayout.setTranslationX(OFFSET_DP * density);
219                 } else {
220                     mLayout.setTranslationY(OFFSET_DP * density);
221                 }
222                 mLayout.animate()
223                         .alpha(1f)
224                         .translationX(0)
225                         .translationY(0)
226                         .setDuration(300)
227                         .setInterpolator(new DecelerateInterpolator())
228                         .start();
229 
230                 ValueAnimator colorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, bgColor);
231                 colorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
232                     @Override
233                     public void onAnimationUpdate(ValueAnimator animation) {
234                         final int c = (Integer) animation.getAnimatedValue();
235                         mColor.setColor(c);
236                     }
237                 });
238                 colorAnim.setDuration(1000);
239                 colorAnim.start();
240             } else {
241                 mColor.setColor(bgColor);
242             }
243 
244             IntentFilter filter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
245             filter.addAction(Intent.ACTION_SCREEN_OFF);
246             mBroadcastDispatcher.registerReceiver(mReceiver, filter);
247             mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
248         }
249 
inflateView(int rotation)250         private void inflateView(int rotation) {
251             // We only want this landscape orientation on <600dp, so rather than handle
252             // resource overlay for -land and -sw600dp-land, just inflate this
253             // other view for this single case.
254             mLayout = (ViewGroup) View.inflate(getContext(),
255                     rotation == ROTATION_SEASCAPE ? R.layout.screen_pinning_request_sea_phone :
256                     rotation == ROTATION_LANDSCAPE ? R.layout.screen_pinning_request_land_phone
257                             : R.layout.screen_pinning_request,
258                     null);
259             // Catch touches so they don't trigger cancel/activate, like outside does.
260             mLayout.setClickable(true);
261             // Status bar is always on the right.
262             mLayout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
263             // Buttons and text do switch sides though.
264             mLayout.findViewById(R.id.screen_pinning_text_area)
265                     .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
266             View buttons = mLayout.findViewById(R.id.screen_pinning_buttons);
267             if (!QuickStepContract.isGesturalMode(mNavBarMode)
268             	    && hasSoftNavigationBar(mContext.getDisplayId()) && !isLargeScreen(mContext)) {
269                 buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
270                 swapChildrenIfRtlAndVertical(buttons);
271             } else {
272                 buttons.setVisibility(View.GONE);
273             }
274 
275             ((Button) mLayout.findViewById(R.id.screen_pinning_ok_button))
276                     .setOnClickListener(ScreenPinningRequest.this);
277             if (mShowCancel) {
278                 ((Button) mLayout.findViewById(R.id.screen_pinning_cancel_button))
279                         .setOnClickListener(ScreenPinningRequest.this);
280             } else {
281                 ((Button) mLayout.findViewById(R.id.screen_pinning_cancel_button))
282                         .setVisibility(View.INVISIBLE);
283             }
284 
285             final Optional<CentralSurfaces> centralSurfacesOptional =
286                     mCentralSurfacesOptionalLazy.get();
287             boolean recentsVisible =
288                     centralSurfacesOptional.map(CentralSurfaces::isOverviewEnabled).orElse(false);
289             boolean touchExplorationEnabled = mAccessibilityService.isTouchExplorationEnabled();
290             int descriptionStringResId;
291             if (QuickStepContract.isGesturalMode(mNavBarMode)) {
292                 descriptionStringResId = R.string.screen_pinning_description_gestural;
293             } else if (recentsVisible) {
294                 mLayout.findViewById(R.id.screen_pinning_recents_group).setVisibility(VISIBLE);
295                 mLayout.findViewById(R.id.screen_pinning_home_bg_light).setVisibility(INVISIBLE);
296                 mLayout.findViewById(R.id.screen_pinning_home_bg).setVisibility(INVISIBLE);
297                 descriptionStringResId = touchExplorationEnabled
298                         ? R.string.screen_pinning_description_accessible
299                         : R.string.screen_pinning_description;
300             } else {
301                 mLayout.findViewById(R.id.screen_pinning_recents_group).setVisibility(INVISIBLE);
302                 mLayout.findViewById(R.id.screen_pinning_home_bg_light).setVisibility(VISIBLE);
303                 mLayout.findViewById(R.id.screen_pinning_home_bg).setVisibility(VISIBLE);
304                 descriptionStringResId = touchExplorationEnabled
305                         ? R.string.screen_pinning_description_recents_invisible_accessible
306                         : R.string.screen_pinning_description_recents_invisible;
307             }
308 
309             NavigationBarView navigationBarView =
310                     centralSurfacesOptional.map(CentralSurfaces::getNavigationBarView).orElse(null);
311             if (navigationBarView != null) {
312                 ((ImageView) mLayout.findViewById(R.id.screen_pinning_back_icon))
313                         .setImageDrawable(navigationBarView.getBackDrawable());
314                 ((ImageView) mLayout.findViewById(R.id.screen_pinning_home_icon))
315                         .setImageDrawable(navigationBarView.getHomeDrawable());
316             }
317 
318             // Create a bulleted list of the default description plus the two security notes.
319             int gapWidth = getResources().getDimensionPixelSize(
320                     R.dimen.screen_pinning_description_bullet_gap_width);
321             SpannableStringBuilder description = new SpannableStringBuilder();
322             description.append(getContext().getText(descriptionStringResId),
323                     new BulletSpan(gapWidth), /* flags */ 0);
324             description.append(System.lineSeparator());
325             description.append(getContext().getText(R.string.screen_pinning_exposes_personal_data),
326                     new BulletSpan(gapWidth), /* flags */ 0);
327             description.append(System.lineSeparator());
328             description.append(getContext().getText(R.string.screen_pinning_can_open_other_apps),
329                     new BulletSpan(gapWidth), /* flags */ 0);
330             ((TextView) mLayout.findViewById(R.id.screen_pinning_description)).setText(description);
331 
332             final int backBgVisibility = touchExplorationEnabled ? View.INVISIBLE : View.VISIBLE;
333             mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility);
334             mLayout.findViewById(R.id.screen_pinning_back_bg_light).setVisibility(backBgVisibility);
335 
336             addView(mLayout, getRequestLayoutParams(rotation));
337         }
338 
339         /**
340          * @param displayId the id of display to check if there is a software navigation bar.
341          *
342          * @return whether there is a soft nav bar on specific display.
343          */
hasSoftNavigationBar(int displayId)344         private boolean hasSoftNavigationBar(int displayId) {
345             try {
346                 return WindowManagerGlobal.getWindowManagerService().hasNavigationBar(displayId);
347             } catch (RemoteException e) {
348                 Log.e(TAG, "Failed to check soft navigation bar", e);
349                 return false;
350             }
351         }
352 
swapChildrenIfRtlAndVertical(View group)353         private void swapChildrenIfRtlAndVertical(View group) {
354             if (mContext.getResources().getConfiguration().getLayoutDirection()
355                     != View.LAYOUT_DIRECTION_RTL) {
356                 return;
357             }
358             LinearLayout linearLayout = (LinearLayout) group;
359             if (linearLayout.getOrientation() == LinearLayout.VERTICAL) {
360                 int childCount = linearLayout.getChildCount();
361                 ArrayList<View> childList = new ArrayList<>(childCount);
362                 for (int i = 0; i < childCount; i++) {
363                     childList.add(linearLayout.getChildAt(i));
364                 }
365                 linearLayout.removeAllViews();
366                 for (int i = childCount - 1; i >= 0; i--) {
367                     linearLayout.addView(childList.get(i));
368                 }
369             }
370         }
371 
372         @Override
onDetachedFromWindow()373         public void onDetachedFromWindow() {
374             mBroadcastDispatcher.unregisterReceiver(mReceiver);
375             mUserTracker.removeCallback(mUserChangedCallback);
376         }
377 
onConfigurationChanged()378         protected void onConfigurationChanged() {
379             removeAllViews();
380             inflateView(getRotation(mContext));
381         }
382 
getRotation(Context context)383         private int getRotation(Context context) {
384             Configuration config = context.getResources().getConfiguration();
385             if (config.smallestScreenWidthDp >= 600) {
386                 return ROTATION_NONE;
387             }
388 
389             return RotationUtils.getRotation(context);
390         }
391 
392         private final Runnable mUpdateLayoutRunnable = new Runnable() {
393             @Override
394             public void run() {
395                 if (mLayout != null && mLayout.getParent() != null) {
396                     mLayout.setLayoutParams(getRequestLayoutParams(getRotation(mContext)));
397                 }
398             }
399         };
400 
401         private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
402             @Override
403             public void onReceive(Context context, Intent intent) {
404                 if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
405                     post(mUpdateLayoutRunnable);
406                 } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
407                     clearPrompt();
408                 }
409             }
410         };
411     }
412 }
413