blob: 9756523637ca18fb39e4c002688ddbc16da6db3b [file] [log] [blame]
Keun-young Park4727da32016-05-31 10:00:51 -07001/*
2 * Copyright (C) 2016 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 */
16package com.android.car.pm;
17
Yao, Yuxing14e83b42018-10-29 18:29:56 -070018import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME;
19import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID;
20import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO;
21import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME;
22
Keun-young Park4727da32016-05-31 10:00:51 -070023import android.app.Activity;
Ram Periathiruvadiffcdb842018-04-16 17:17:39 -070024import android.car.Car;
Yao, Yuxingd1d6a372018-05-08 10:37:43 -070025import android.car.content.pm.CarPackageManager;
Ram Periathiruvadiffcdb842018-04-16 17:17:39 -070026import android.car.drivingstate.CarUxRestrictions;
27import android.car.drivingstate.CarUxRestrictionsManager;
Keun-young Park4727da32016-05-31 10:00:51 -070028import android.content.ComponentName;
Yao, Yuxingd1d6a372018-05-08 10:37:43 -070029import android.content.Intent;
Yao, Yuxing14e83b42018-10-29 18:29:56 -070030import android.content.pm.ApplicationInfo;
31import android.content.pm.PackageManager;
32import android.graphics.drawable.Drawable;
33import android.os.Build;
Keun-young Park4727da32016-05-31 10:00:51 -070034import android.os.Bundle;
Yao, Yuxing14e83b42018-10-29 18:29:56 -070035import android.text.TextUtils;
Ram Periathiruvadiffcdb842018-04-16 17:17:39 -070036import android.util.Log;
Yao, Yuxingd1d6a372018-05-08 10:37:43 -070037import android.view.View;
38import android.widget.Button;
Yao, Yuxing14e83b42018-10-29 18:29:56 -070039import android.widget.ImageView;
Yao, Yuxing66644122018-04-02 10:08:33 -070040import android.widget.TextView;
Keun-young Park4727da32016-05-31 10:00:51 -070041
Yao, Yuxing14e83b42018-10-29 18:29:56 -070042import androidx.annotation.Nullable;
43
Ram Periathiruvadiffcdb842018-04-16 17:17:39 -070044import com.android.car.CarLog;
Keun-young Park4727da32016-05-31 10:00:51 -070045import com.android.car.R;
46
47/**
48 * Default activity that will be launched when the current foreground activity is not allowed.
Yao, Yuxing14e83b42018-10-29 18:29:56 -070049 * Additional information on blocked Activity should be passed as intent extras.
Keun-young Park4727da32016-05-31 10:00:51 -070050 */
51public class ActivityBlockingActivity extends Activity {
Yao, Yuxingd1d6a372018-05-08 10:37:43 -070052 private static final int INVALID_TASK_ID = -1;
Yao, Yuxing66644122018-04-02 10:08:33 -070053
Ram Periathiruvadiffcdb842018-04-16 17:17:39 -070054 private Car mCar;
55 private CarUxRestrictionsManager mUxRManager;
56
Yao, Yuxing14e83b42018-10-29 18:29:56 -070057 private TextView mBlockedAppName;
58 private ImageView mBlockedAppIcon;
Yao, Yuxingf795fa92018-11-30 16:41:13 -080059 private TextView mBlockingText;
60 private TextView mExitButtonMessage;
Yao, Yuxingd1d6a372018-05-08 10:37:43 -070061 private Button mExitButton;
Yao, Yuxing14e83b42018-10-29 18:29:56 -070062
Yao, Yuxingd1d6a372018-05-08 10:37:43 -070063 private int mBlockedTaskId;
64
Keun-young Park4727da32016-05-31 10:00:51 -070065 @Override
66 protected void onCreate(Bundle savedInstanceState) {
67 super.onCreate(savedInstanceState);
68 setContentView(R.layout.activity_blocking);
Keun-young Park4727da32016-05-31 10:00:51 -070069
Yao, Yuxing14e83b42018-10-29 18:29:56 -070070 mBlockingText = findViewById(R.id.blocking_text);
71 mBlockedAppName = findViewById(R.id.blocked_app_name);
72 mBlockedAppIcon = findViewById(R.id.blocked_app_icon);
Yao, Yuxingf795fa92018-11-30 16:41:13 -080073 mExitButton = findViewById(R.id.exit_button);
74 mExitButtonMessage = findViewById(R.id.exit_button_message);
Yao, Yuxing14e83b42018-10-29 18:29:56 -070075
76 mBlockingText.setText(getString(R.string.activity_blocked_text));
Ram Periathiruvadiffcdb842018-04-16 17:17:39 -070077
78 // Listen to the CarUxRestrictions so this blocking activity can be dismissed when the
79 // restrictions are lifted.
Keun young Parka5fa4782019-04-16 18:56:27 -070080 // This Activity should be launched only after car service is initialized. Currently this
81 // Activity is only launched from CPMS. So this is safe to do.
Keun young Parkee220032019-10-11 16:13:34 -070082 mCar = Car.createCar(this, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
83 (car, ready) -> {
84 if (!ready) {
85 return;
86 }
87 mUxRManager = (CarUxRestrictionsManager) car.getCarManager(
88 Car.CAR_UX_RESTRICTION_SERVICE);
89 // This activity would have been launched only in a restricted state.
90 // But ensuring when the service connection is established, that we are still
91 // in a restricted state.
92 handleUxRChange(mUxRManager.getCurrentCarUxRestrictions());
93 mUxRManager.registerListener(ActivityBlockingActivity.this::handleUxRChange);
94 });
Yao, Yuxing13ae2902018-04-12 16:20:34 -070095 }
96
Ram Periathiruvadiffcdb842018-04-16 17:17:39 -070097 @Override
98 protected void onResume() {
99 super.onResume();
Yao, Yuxingd1d6a372018-05-08 10:37:43 -0700100
Yao, Yuxing14e83b42018-10-29 18:29:56 -0700101 // Display info about the current blocked activity, and optionally show an exit button
Yao, Yuxingd1d6a372018-05-08 10:37:43 -0700102 // to restart the blocked task (stack of activities) if its root activity is DO.
Yao, Yuxing14e83b42018-10-29 18:29:56 -0700103 mBlockedTaskId = getIntent().getIntExtra(BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID,
104 INVALID_TASK_ID);
Yao, Yuxingd1d6a372018-05-08 10:37:43 -0700105
106 // blockedActivity is expected to be always passed in as the topmost activity of task.
Yao, Yuxing14e83b42018-10-29 18:29:56 -0700107 String blockedActivity = getIntent().getStringExtra(
108 BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME);
109 if (!TextUtils.isEmpty(blockedActivity)) {
110 if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
111 Log.d(CarLog.TAG_AM, "Blocking activity " + blockedActivity);
112 }
113 // Show application icon and name of blocked activity.
114 Drawable appIcon = findApplicationIcon(blockedActivity);
115 if (appIcon != null) {
116 mBlockedAppIcon.setImageDrawable(appIcon);
117 } else {
118 mBlockedAppIcon.setVisibility(View.GONE);
119 }
120 mBlockedAppName.setText(findHumanReadableLabel(blockedActivity));
Yao, Yuxingd1d6a372018-05-08 10:37:43 -0700121 }
122
Yao, Yuxing14e83b42018-10-29 18:29:56 -0700123 boolean isRootDO = getIntent().getBooleanExtra(
124 BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO, false);
Yao, Yuxingd1d6a372018-05-08 10:37:43 -0700125
Yao, Yuxing14e83b42018-10-29 18:29:56 -0700126 // Display a button to restart task if root task is DO.
127 boolean showButton = mBlockedTaskId != INVALID_TASK_ID && isRootDO;
128 mExitButton.setVisibility(showButton ? View.VISIBLE : View.GONE);
129 mExitButton.setOnClickListener(v -> handleRestartingTask());
Yao, Yuxingf795fa92018-11-30 16:41:13 -0800130 mExitButtonMessage.setVisibility(showButton ? View.VISIBLE : View.GONE);
131 mExitButtonMessage.setText(
132 getString(R.string.exit_button_message, getString(R.string.exit_button)));
Yao, Yuxing14e83b42018-10-29 18:29:56 -0700133
134 // Show more debug info for non-user build.
135 if (Build.IS_ENG || Build.IS_USERDEBUG) {
136 displayDebugInfo();
Yao, Yuxingd1d6a372018-05-08 10:37:43 -0700137 }
138 }
139
Yao, Yuxing14e83b42018-10-29 18:29:56 -0700140 private void displayDebugInfo() {
141 String blockedActivity = getIntent().getStringExtra(
142 BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME);
143 String rootActivity = getIntent().getStringExtra(BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME);
144
145 TextView debugInfo = findViewById(R.id.debug_info);
146 debugInfo.setText(getDebugInfo(blockedActivity, rootActivity));
147
148 // We still want to ensure driving safety for non-user build;
149 // toggle visibility of debug info with this button.
150 Button toggleDebug = findViewById(R.id.toggle_debug_info);
151 toggleDebug.setVisibility(View.VISIBLE);
152 toggleDebug.setOnClickListener(v -> {
153 boolean isDebugVisible = debugInfo.getVisibility() == View.VISIBLE;
154 debugInfo.setVisibility(isDebugVisible ? View.GONE : View.VISIBLE);
155 });
156 }
157
158 private String getDebugInfo(String blockedActivity, String rootActivity) {
159 StringBuilder debug = new StringBuilder();
160
161 ComponentName blocked = ComponentName.unflattenFromString(blockedActivity);
162 debug.append("Blocked activity is ")
163 .append(blocked.getShortClassName())
164 .append("\nBlocked activity package is ")
165 .append(blocked.getPackageName());
166
167 if (rootActivity != null) {
168 ComponentName root = ComponentName.unflattenFromString(rootActivity);
169 // Optionally show root activity info if it differs from the blocked activity.
170 if (!root.equals(blocked)) {
171 debug.append("\n\nRoot activity is ").append(root.getShortClassName());
172 }
173 if (!root.getPackageName().equals(blocked.getPackageName())) {
174 debug.append("\nRoot activity package is ").append(root.getPackageName());
175 }
176 }
177 return debug.toString();
178 }
179
Yao, Yuxingd1d6a372018-05-08 10:37:43 -0700180 @Override
181 protected void onNewIntent(Intent intent) {
182 super.onNewIntent(intent);
183 setIntent(intent);
Ram Periathiruvadiffcdb842018-04-16 17:17:39 -0700184 }
185
186 @Override
Yao, Yuxing2ef22c92018-08-23 16:20:24 -0700187 protected void onStop() {
188 super.onStop();
189 // Finish when blocking activity goes invisible to avoid it accidentally re-surfaces with
190 // stale string regarding blocked activity.
191 finish();
192 }
193
194 @Override
Ram Periathiruvadiffcdb842018-04-16 17:17:39 -0700195 protected void onDestroy() {
196 super.onDestroy();
Keun young Parka5fa4782019-04-16 18:56:27 -0700197 mUxRManager.unregisterListener();
Ram Periathiruvadiffcdb842018-04-16 17:17:39 -0700198 }
199
200 // If no distraction optimization is required in the new restrictions, then dismiss the
201 // blocking activity (self).
202 private void handleUxRChange(CarUxRestrictions restrictions) {
203 if (restrictions == null) {
204 return;
205 }
206 if (!restrictions.isRequiresDistractionOptimization()) {
207 finish();
208 }
209 }
Yao, Yuxingd1d6a372018-05-08 10:37:43 -0700210
Yao, Yuxing14e83b42018-10-29 18:29:56 -0700211 // Finds the icon of the application (package) the component belongs to.
212 @Nullable
213 private Drawable findApplicationIcon(String flattenComponentName) {
214 ComponentName componentName = ComponentName.unflattenFromString(flattenComponentName);
215 try {
216 return getPackageManager().getApplicationIcon(componentName.getPackageName());
217 } catch (PackageManager.NameNotFoundException e) {
218 if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
219 Log.i(CarLog.TAG_AM, "Could not find package for component name "
220 + componentName.toString());
221 }
222 }
223 return null;
224 }
225
226 /**
227 * Returns a human-readable string for {@code flattenComponentName}.
228 *
229 * <p>It first attempts to return the application label for this activity. If that fails,
230 * it will return the last part in the activity name.
231 */
232 private String findHumanReadableLabel(String flattenComponentName) {
233 ComponentName componentName = ComponentName.unflattenFromString(flattenComponentName);
234 String label = null;
235 // Attempt to find application label.
236 try {
237 ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
238 componentName.getPackageName(), 0);
239 CharSequence appLabel = getPackageManager().getApplicationLabel(applicationInfo);
240 if (appLabel != null) {
241 label = appLabel.toString();
242 }
243 } catch (PackageManager.NameNotFoundException e) {
244 if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
245 Log.i(CarLog.TAG_AM, "Could not find package for component name "
246 + componentName.toString());
247 }
248 }
249 if (TextUtils.isEmpty(label)) {
250 label = componentName.getClass().getSimpleName();
251 }
252 return label;
253 }
254
255 private void handleRestartingTask() {
Yao, Yuxingd1d6a372018-05-08 10:37:43 -0700256 if (isFinishing()) {
257 return;
258 }
259
Yao, Yuxing14e83b42018-10-29 18:29:56 -0700260 // Lock on self to avoid restarting the same task twice.
Yao, Yuxingd1d6a372018-05-08 10:37:43 -0700261 synchronized (this) {
Justin Paupore97b4df72019-02-12 19:40:39 -0800262 if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
263 Log.i(CarLog.TAG_AM, "Restarting task " + mBlockedTaskId);
Yao, Yuxingd1d6a372018-05-08 10:37:43 -0700264 }
Justin Paupore97b4df72019-02-12 19:40:39 -0800265 CarPackageManager carPm = (CarPackageManager)
266 mCar.getCarManager(Car.PACKAGE_SERVICE);
267 carPm.restartTask(mBlockedTaskId);
Yao, Yuxingd1d6a372018-05-08 10:37:43 -0700268 finish();
269 }
270 }
Keun-young Park4727da32016-05-31 10:00:51 -0700271}