• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 package com.android.systemui.car.activity;
17 
18 import android.app.Activity;
19 import android.app.ActivityManager;
20 import android.car.Car;
21 import android.car.CarOccupantZoneManager;
22 import android.car.app.CarActivityManager;
23 import android.car.content.pm.CarPackageManager;
24 import android.car.drivingstate.CarUxRestrictions;
25 import android.car.drivingstate.CarUxRestrictionsManager;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.graphics.Insets;
30 import android.graphics.PixelFormat;
31 import android.graphics.Rect;
32 import android.hardware.display.DisplayManager;
33 import android.opengl.GLSurfaceView;
34 import android.os.Build;
35 import android.os.Bundle;
36 import android.os.Handler;
37 import android.os.UserHandle;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.util.Slog;
41 import android.view.DisplayInfo;
42 import android.view.View;
43 import android.view.ViewTreeObserver;
44 import android.view.WindowInsets;
45 import android.widget.Button;
46 import android.widget.TextView;
47 
48 import com.android.systemui.R;
49 import com.android.systemui.car.activity.blurredbackground.BlurredSurfaceRenderer;
50 
51 import java.util.List;
52 
53 /**
54  * Default activity that will be launched when the current foreground activity is not allowed.
55  * Additional information on blocked Activity should be passed as intent extras.
56  */
57 public class ActivityBlockingActivity extends Activity {
58     private static final int ACTIVITY_MONITORING_DELAY_MS = 1000;
59     private static final String TAG = "BlockingActivity";
60     private static final int EGL_CONTEXT_VERSION = 2;
61     private static final int EGL_CONFIG_SIZE = 8;
62     private static final int INVALID_TASK_ID = -1;
63     private final Object mLock = new Object();
64 
65     private GLSurfaceView mGLSurfaceView;
66     private BlurredSurfaceRenderer mSurfaceRenderer;
67     private boolean mIsGLSurfaceSetup = false;
68 
69     private Car mCar;
70     private CarUxRestrictionsManager mUxRManager;
71     private CarPackageManager mCarPackageManager;
72     private CarActivityManager mCarActivityManager;
73     private CarOccupantZoneManager mCarOccupantZoneManager;
74 
75     private Button mExitButton;
76     private Button mToggleDebug;
77 
78     private int mBlockedTaskId;
79     private final Handler mHandler = new Handler();
80 
81     private final View.OnClickListener mOnExitButtonClickedListener =
82             v -> {
83                 if (isExitOptionCloseApplication()) {
84                     handleCloseApplication();
85                 } else {
86                     handleRestartingTask();
87                 }
88             };
89 
90     private final ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener =
91             new ViewTreeObserver.OnGlobalLayoutListener() {
92                 @Override
93                 public void onGlobalLayout() {
94                     mToggleDebug.getViewTreeObserver().removeOnGlobalLayoutListener(this);
95                     updateButtonWidths();
96                 }
97             };
98 
99     @Override
onCreate(Bundle savedInstanceState)100     protected void onCreate(Bundle savedInstanceState) {
101         super.onCreate(savedInstanceState);
102         setContentView(R.layout.activity_blocking);
103 
104         mExitButton = findViewById(R.id.exit_button);
105 
106         // Listen to the CarUxRestrictions so this blocking activity can be dismissed when the
107         // restrictions are lifted.
108         // This Activity should be launched only after car service is initialized. Currently this
109         // Activity is only launched from CPMS. So this is safe to do.
110         mCar = Car.createCar(this, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
111                 (car, ready) -> {
112                     if (!ready) {
113                         return;
114                     }
115                     mCarPackageManager = (CarPackageManager) car.getCarManager(
116                             Car.PACKAGE_SERVICE);
117                     mCarActivityManager = (CarActivityManager) car.getCarManager(
118                             Car.CAR_ACTIVITY_SERVICE);
119                     mUxRManager = (CarUxRestrictionsManager) car.getCarManager(
120                             Car.CAR_UX_RESTRICTION_SERVICE);
121                     mCarOccupantZoneManager = car.getCarManager(CarOccupantZoneManager.class);
122                     // This activity would have been launched only in a restricted state.
123                     // But ensuring when the service connection is established, that we are still
124                     // in a restricted state.
125                     handleUxRChange(mUxRManager.getCurrentCarUxRestrictions());
126                     mUxRManager.registerListener(ActivityBlockingActivity.this::handleUxRChange);
127                 });
128 
129         setupGLSurface();
130     }
131 
132     @Override
onStart()133     protected void onStart() {
134         super.onStart();
135         if (mIsGLSurfaceSetup) {
136             mGLSurfaceView.onResume();
137         }
138     }
139 
140     @Override
onResume()141     protected void onResume() {
142         super.onResume();
143 
144         // Display info about the current blocked activity, and optionally show an exit button
145         // to restart the blocked task (stack of activities) if its root activity is DO.
146         mBlockedTaskId = getIntent().getIntExtra(
147                 CarPackageManager.BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID,
148                 INVALID_TASK_ID);
149 
150         // blockedActivity is expected to be always passed in as the topmost activity of task.
151         String blockedActivity = getIntent().getStringExtra(
152                 CarPackageManager.BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME);
153         if (!TextUtils.isEmpty(blockedActivity)) {
154             boolean finished = finishIfActivitiesAreDistractionOptimised();
155             if (finished) {
156                 return;
157             }
158 
159             if (Log.isLoggable(TAG, Log.DEBUG)) {
160                 Slog.d(TAG, "Blocking activity " + blockedActivity);
161             }
162         }
163 
164         displayExitButton();
165 
166         // Show more debug info for non-user build.
167         if (Build.IS_ENG || Build.IS_USERDEBUG) {
168             displayDebugInfo();
169         }
170     }
171 
172     @Override
onStop()173     protected void onStop() {
174         super.onStop();
175 
176         if (mIsGLSurfaceSetup) {
177             // We queue this event so that it runs on the Rendering thread
178             mGLSurfaceView.queueEvent(() -> mSurfaceRenderer.onPause());
179 
180             mGLSurfaceView.onPause();
181         }
182 
183         // Finish when blocking activity goes invisible to avoid it accidentally re-surfaces with
184         // stale string regarding blocked activity.
185         finish();
186     }
187 
finishIfActivitiesAreDistractionOptimised()188     private boolean finishIfActivitiesAreDistractionOptimised() {
189         if (areAllVisibleActivitiesDistractionOptimised()) {
190             Slog.i(TAG, "All visible activities are already DO, so finishing");
191             finish();
192             return true;
193         }
194         mHandler.postDelayed(() -> finishIfActivitiesAreDistractionOptimised(),
195                 ACTIVITY_MONITORING_DELAY_MS);
196         return false;
197     }
198 
setupGLSurface()199     private void setupGLSurface() {
200         DisplayManager displayManager = (DisplayManager) getApplicationContext().getSystemService(
201                 Context.DISPLAY_SERVICE);
202         DisplayInfo displayInfo = new DisplayInfo();
203 
204         int displayId = getDisplayId();
205         displayManager.getDisplay(displayId).getDisplayInfo(displayInfo);
206 
207         Rect windowRect = getAppWindowRect();
208 
209         mSurfaceRenderer = new BlurredSurfaceRenderer(this, windowRect, getDisplayId());
210 
211         mGLSurfaceView = findViewById(R.id.blurred_surface_view);
212         mGLSurfaceView.setEGLContextClientVersion(EGL_CONTEXT_VERSION);
213 
214         // Sets up the surface so that we can make it translucent if needed
215         mGLSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
216         mGLSurfaceView.setEGLConfigChooser(EGL_CONFIG_SIZE, EGL_CONFIG_SIZE, EGL_CONFIG_SIZE,
217                 EGL_CONFIG_SIZE, EGL_CONFIG_SIZE, EGL_CONFIG_SIZE);
218 
219         mGLSurfaceView.setRenderer(mSurfaceRenderer);
220 
221         // We only want to render the screen once
222         mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
223 
224         mIsGLSurfaceSetup = true;
225     }
226 
227     /**
228      * Computes a Rect that represents the portion of the screen that contains the activity that is
229      * being blocked.
230      *
231      * @return Rect that represents the application window
232      */
getAppWindowRect()233     private Rect getAppWindowRect() {
234         Insets systemBarInsets = getWindowManager()
235                 .getCurrentWindowMetrics()
236                 .getWindowInsets()
237                 .getInsets(WindowInsets.Type.systemBars());
238 
239         Rect displayBounds = getWindowManager().getCurrentWindowMetrics().getBounds();
240 
241         int leftX = systemBarInsets.left;
242         int rightX = displayBounds.width() - systemBarInsets.right;
243         int topY = systemBarInsets.top;
244         int bottomY = displayBounds.height() - systemBarInsets.bottom;
245 
246         return new Rect(leftX, topY, rightX, bottomY);
247     }
248 
displayExitButton()249     private void displayExitButton() {
250         String exitButtonText = getExitButtonText();
251 
252         mExitButton.setText(exitButtonText);
253         mExitButton.setOnClickListener(mOnExitButtonClickedListener);
254     }
255 
256     // If the root activity is DO, the user will have the option to go back to that activity,
257     // otherwise, the user will have the option to close the blocked application
isExitOptionCloseApplication()258     private boolean isExitOptionCloseApplication() {
259         boolean isRootDO = getIntent().getBooleanExtra(
260                 CarPackageManager.BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO, false);
261         return mBlockedTaskId == INVALID_TASK_ID || !isRootDO;
262     }
263 
getExitButtonText()264     private String getExitButtonText() {
265         return isExitOptionCloseApplication() ? getString(R.string.exit_button_close_application)
266                 : getString(R.string.exit_button_go_back);
267     }
268 
269     /**
270      * It is possible that the stack info has changed between when the intent to launch this
271      * activity was initiated and when this activity is started. Check whether all the visible
272      * activities are distraction optimized.
273      */
areAllVisibleActivitiesDistractionOptimised()274     private boolean areAllVisibleActivitiesDistractionOptimised() {
275         List<ActivityManager.RunningTaskInfo> visibleTasks;
276         visibleTasks = mCarActivityManager.getVisibleTasks();
277         for (int i = visibleTasks.size() - 1; i >= 0; i--) {
278             ActivityManager.RunningTaskInfo taskInfo = visibleTasks.get(i);
279             if (taskInfo.displayId != getDisplayId()) {
280                 // ignore stacks on other displays
281                 continue;
282             }
283 
284             if (getComponentName().equals(taskInfo.topActivity)) {
285                 // skip the ActivityBlockingActivity itself
286                 continue;
287             }
288 
289             if (taskInfo.topActivity != null) {
290                 boolean isDo = mCarPackageManager.isActivityDistractionOptimized(
291                         taskInfo.topActivity.getPackageName(),
292                         taskInfo.topActivity.getClassName());
293                 if (Log.isLoggable(TAG, Log.DEBUG)) {
294                     Slog.d(TAG,
295                             String.format("Activity (%s) is DO: %s", taskInfo.topActivity, isDo));
296                 }
297                 if (!isDo) {
298                     return false;
299                 }
300             }
301         }
302 
303         // No visible non-DO activity found.
304         return true;
305     }
306 
displayDebugInfo()307     private void displayDebugInfo() {
308         String blockedActivity = getIntent().getStringExtra(
309                 CarPackageManager.BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME);
310         String rootActivity = getIntent().getStringExtra(
311                 CarPackageManager.BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME);
312 
313         TextView debugInfo = findViewById(R.id.debug_info);
314         debugInfo.setText(getDebugInfo(blockedActivity, rootActivity));
315 
316         // We still want to ensure driving safety for non-user build;
317         // toggle visibility of debug info with this button.
318         mToggleDebug = findViewById(R.id.toggle_debug_info);
319         mToggleDebug.setVisibility(View.VISIBLE);
320         mToggleDebug.setOnClickListener(v -> {
321             boolean isDebugVisible = debugInfo.getVisibility() == View.VISIBLE;
322             debugInfo.setVisibility(isDebugVisible ? View.GONE : View.VISIBLE);
323         });
324 
325         mToggleDebug.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
326     }
327 
328     // When the Debug button is visible, we set both of the visible buttons to have the width
329     // of whichever button is wider
updateButtonWidths()330     private void updateButtonWidths() {
331         Button debugButton = findViewById(R.id.toggle_debug_info);
332 
333         int exitButtonWidth = mExitButton.getWidth();
334         int debugButtonWidth = debugButton.getWidth();
335 
336         if (exitButtonWidth > debugButtonWidth) {
337             debugButton.setWidth(exitButtonWidth);
338         } else {
339             mExitButton.setWidth(debugButtonWidth);
340         }
341     }
342 
getDebugInfo(String blockedActivity, String rootActivity)343     private String getDebugInfo(String blockedActivity, String rootActivity) {
344         StringBuilder debug = new StringBuilder();
345 
346         ComponentName blocked = ComponentName.unflattenFromString(blockedActivity);
347         debug.append("Blocked activity is ")
348                 .append(blocked.getShortClassName())
349                 .append("\nBlocked activity package is ")
350                 .append(blocked.getPackageName());
351 
352         if (rootActivity != null) {
353             ComponentName root = ComponentName.unflattenFromString(rootActivity);
354             // Optionally show root activity info if it differs from the blocked activity.
355             if (!root.equals(blocked)) {
356                 debug.append("\n\nRoot activity is ").append(root.getShortClassName());
357             }
358             if (!root.getPackageName().equals(blocked.getPackageName())) {
359                 debug.append("\nRoot activity package is ").append(root.getPackageName());
360             }
361         }
362         return debug.toString();
363     }
364 
365     @Override
onNewIntent(Intent intent)366     protected void onNewIntent(Intent intent) {
367         super.onNewIntent(intent);
368         setIntent(intent);
369     }
370 
371     @Override
onDestroy()372     protected void onDestroy() {
373         super.onDestroy();
374         mCar.disconnect();
375         mUxRManager.unregisterListener();
376         if (mToggleDebug != null) {
377             mToggleDebug.getViewTreeObserver().removeOnGlobalLayoutListener(
378                     mOnGlobalLayoutListener);
379         }
380         mHandler.removeCallbacksAndMessages(null);
381         mCar.disconnect();
382     }
383 
384     // If no distraction optimization is required in the new restrictions, then dismiss the
385     // blocking activity (self).
handleUxRChange(CarUxRestrictions restrictions)386     private void handleUxRChange(CarUxRestrictions restrictions) {
387         if (restrictions == null) {
388             return;
389         }
390         if (!restrictions.isRequiresDistractionOptimization()) {
391             finish();
392         }
393     }
394 
handleCloseApplication()395     private void handleCloseApplication() {
396         if (isFinishing()) {
397             return;
398         }
399 
400         int displayId = getDisplayId();
401         int userOnDisplay = mCarOccupantZoneManager.getUserForDisplayId(displayId);
402         if (userOnDisplay == CarOccupantZoneManager.INVALID_USER_ID) {
403             Slog.e(TAG, "can not find user on display " + displayId
404                     + " to start Home");
405             finish();
406         }
407 
408         Intent startMain = new Intent(Intent.ACTION_MAIN);
409 
410         int driverDisplayId = mCarOccupantZoneManager.getDisplayIdForDriver(
411                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
412         if (Log.isLoggable(TAG, Log.DEBUG)) {
413             Slog.d(TAG, String.format("display id: %d, driver display id: %d",
414                     displayId, driverDisplayId));
415         }
416         startMain.addCategory(Intent.CATEGORY_HOME);
417         startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
418         startActivityAsUser(startMain, UserHandle.of(userOnDisplay));
419         finish();
420     }
421 
handleRestartingTask()422     private void handleRestartingTask() {
423         // Lock on self to avoid restarting the same task twice.
424         synchronized (mLock) {
425             if (isFinishing()) {
426                 return;
427             }
428 
429             if (Log.isLoggable(TAG, Log.INFO)) {
430                 Slog.i(TAG, "Restarting task " + mBlockedTaskId);
431             }
432             mCarPackageManager.restartTask(mBlockedTaskId);
433             finish();
434         }
435     }
436 }
437