Keun-young Park | 4727da3 | 2016-05-31 10:00:51 -0700 | [diff] [blame] | 1 | /* |
| 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 | */ |
| 16 | package com.android.car.pm; |
| 17 | |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 18 | import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME; |
| 19 | import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID; |
| 20 | import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO; |
| 21 | import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME; |
| 22 | |
Keun-young Park | 4727da3 | 2016-05-31 10:00:51 -0700 | [diff] [blame] | 23 | import android.app.Activity; |
Ram Periathiruvadi | ffcdb84 | 2018-04-16 17:17:39 -0700 | [diff] [blame] | 24 | import android.car.Car; |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 25 | import android.car.content.pm.CarPackageManager; |
Ram Periathiruvadi | ffcdb84 | 2018-04-16 17:17:39 -0700 | [diff] [blame] | 26 | import android.car.drivingstate.CarUxRestrictions; |
| 27 | import android.car.drivingstate.CarUxRestrictionsManager; |
Keun-young Park | 4727da3 | 2016-05-31 10:00:51 -0700 | [diff] [blame] | 28 | import android.content.ComponentName; |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 29 | import android.content.Intent; |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 30 | import android.content.pm.ApplicationInfo; |
| 31 | import android.content.pm.PackageManager; |
| 32 | import android.graphics.drawable.Drawable; |
| 33 | import android.os.Build; |
Keun-young Park | 4727da3 | 2016-05-31 10:00:51 -0700 | [diff] [blame] | 34 | import android.os.Bundle; |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 35 | import android.text.TextUtils; |
Ram Periathiruvadi | ffcdb84 | 2018-04-16 17:17:39 -0700 | [diff] [blame] | 36 | import android.util.Log; |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 37 | import android.view.View; |
| 38 | import android.widget.Button; |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 39 | import android.widget.ImageView; |
Yao, Yuxing | 6664412 | 2018-04-02 10:08:33 -0700 | [diff] [blame] | 40 | import android.widget.TextView; |
Keun-young Park | 4727da3 | 2016-05-31 10:00:51 -0700 | [diff] [blame] | 41 | |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 42 | import androidx.annotation.Nullable; |
| 43 | |
Ram Periathiruvadi | ffcdb84 | 2018-04-16 17:17:39 -0700 | [diff] [blame] | 44 | import com.android.car.CarLog; |
Keun-young Park | 4727da3 | 2016-05-31 10:00:51 -0700 | [diff] [blame] | 45 | import com.android.car.R; |
| 46 | |
| 47 | /** |
| 48 | * Default activity that will be launched when the current foreground activity is not allowed. |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 49 | * Additional information on blocked Activity should be passed as intent extras. |
Keun-young Park | 4727da3 | 2016-05-31 10:00:51 -0700 | [diff] [blame] | 50 | */ |
| 51 | public class ActivityBlockingActivity extends Activity { |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 52 | private static final int INVALID_TASK_ID = -1; |
Yao, Yuxing | 6664412 | 2018-04-02 10:08:33 -0700 | [diff] [blame] | 53 | |
Ram Periathiruvadi | ffcdb84 | 2018-04-16 17:17:39 -0700 | [diff] [blame] | 54 | private Car mCar; |
| 55 | private CarUxRestrictionsManager mUxRManager; |
| 56 | |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 57 | private TextView mBlockedAppName; |
| 58 | private ImageView mBlockedAppIcon; |
Yao, Yuxing | f795fa9 | 2018-11-30 16:41:13 -0800 | [diff] [blame] | 59 | private TextView mBlockingText; |
| 60 | private TextView mExitButtonMessage; |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 61 | private Button mExitButton; |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 62 | |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 63 | private int mBlockedTaskId; |
| 64 | |
Keun-young Park | 4727da3 | 2016-05-31 10:00:51 -0700 | [diff] [blame] | 65 | @Override |
| 66 | protected void onCreate(Bundle savedInstanceState) { |
| 67 | super.onCreate(savedInstanceState); |
| 68 | setContentView(R.layout.activity_blocking); |
Keun-young Park | 4727da3 | 2016-05-31 10:00:51 -0700 | [diff] [blame] | 69 | |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 70 | mBlockingText = findViewById(R.id.blocking_text); |
| 71 | mBlockedAppName = findViewById(R.id.blocked_app_name); |
| 72 | mBlockedAppIcon = findViewById(R.id.blocked_app_icon); |
Yao, Yuxing | f795fa9 | 2018-11-30 16:41:13 -0800 | [diff] [blame] | 73 | mExitButton = findViewById(R.id.exit_button); |
| 74 | mExitButtonMessage = findViewById(R.id.exit_button_message); |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 75 | |
| 76 | mBlockingText.setText(getString(R.string.activity_blocked_text)); |
Ram Periathiruvadi | ffcdb84 | 2018-04-16 17:17:39 -0700 | [diff] [blame] | 77 | |
| 78 | // Listen to the CarUxRestrictions so this blocking activity can be dismissed when the |
| 79 | // restrictions are lifted. |
Keun young Park | a5fa478 | 2019-04-16 18:56:27 -0700 | [diff] [blame] | 80 | // 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 Park | ee22003 | 2019-10-11 16:13:34 -0700 | [diff] [blame] | 82 | 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, Yuxing | 13ae290 | 2018-04-12 16:20:34 -0700 | [diff] [blame] | 95 | } |
| 96 | |
Ram Periathiruvadi | ffcdb84 | 2018-04-16 17:17:39 -0700 | [diff] [blame] | 97 | @Override |
| 98 | protected void onResume() { |
| 99 | super.onResume(); |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 100 | |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 101 | // Display info about the current blocked activity, and optionally show an exit button |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 102 | // to restart the blocked task (stack of activities) if its root activity is DO. |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 103 | mBlockedTaskId = getIntent().getIntExtra(BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID, |
| 104 | INVALID_TASK_ID); |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 105 | |
| 106 | // blockedActivity is expected to be always passed in as the topmost activity of task. |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 107 | 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, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 121 | } |
| 122 | |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 123 | boolean isRootDO = getIntent().getBooleanExtra( |
| 124 | BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO, false); |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 125 | |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 126 | // 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, Yuxing | f795fa9 | 2018-11-30 16:41:13 -0800 | [diff] [blame] | 130 | mExitButtonMessage.setVisibility(showButton ? View.VISIBLE : View.GONE); |
| 131 | mExitButtonMessage.setText( |
| 132 | getString(R.string.exit_button_message, getString(R.string.exit_button))); |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 133 | |
| 134 | // Show more debug info for non-user build. |
| 135 | if (Build.IS_ENG || Build.IS_USERDEBUG) { |
| 136 | displayDebugInfo(); |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 137 | } |
| 138 | } |
| 139 | |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 140 | 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, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 180 | @Override |
| 181 | protected void onNewIntent(Intent intent) { |
| 182 | super.onNewIntent(intent); |
| 183 | setIntent(intent); |
Ram Periathiruvadi | ffcdb84 | 2018-04-16 17:17:39 -0700 | [diff] [blame] | 184 | } |
| 185 | |
| 186 | @Override |
Yao, Yuxing | 2ef22c9 | 2018-08-23 16:20:24 -0700 | [diff] [blame] | 187 | 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 Periathiruvadi | ffcdb84 | 2018-04-16 17:17:39 -0700 | [diff] [blame] | 195 | protected void onDestroy() { |
| 196 | super.onDestroy(); |
Keun young Park | a5fa478 | 2019-04-16 18:56:27 -0700 | [diff] [blame] | 197 | mUxRManager.unregisterListener(); |
Ram Periathiruvadi | ffcdb84 | 2018-04-16 17:17:39 -0700 | [diff] [blame] | 198 | } |
| 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, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 210 | |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 211 | // 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, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 256 | if (isFinishing()) { |
| 257 | return; |
| 258 | } |
| 259 | |
Yao, Yuxing | 14e83b4 | 2018-10-29 18:29:56 -0700 | [diff] [blame] | 260 | // Lock on self to avoid restarting the same task twice. |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 261 | synchronized (this) { |
Justin Paupore | 97b4df7 | 2019-02-12 19:40:39 -0800 | [diff] [blame] | 262 | if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) { |
| 263 | Log.i(CarLog.TAG_AM, "Restarting task " + mBlockedTaskId); |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 264 | } |
Justin Paupore | 97b4df7 | 2019-02-12 19:40:39 -0800 | [diff] [blame] | 265 | CarPackageManager carPm = (CarPackageManager) |
| 266 | mCar.getCarManager(Car.PACKAGE_SERVICE); |
| 267 | carPm.restartTask(mBlockedTaskId); |
Yao, Yuxing | d1d6a37 | 2018-05-08 10:37:43 -0700 | [diff] [blame] | 268 | finish(); |
| 269 | } |
| 270 | } |
Keun-young Park | 4727da3 | 2016-05-31 10:00:51 -0700 | [diff] [blame] | 271 | } |