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 18 import android.app.Activity; 19 import android.car.Car; 20 import android.car.CarNotConnectedException; 21 import android.car.content.pm.CarPackageManager; 22 import android.car.drivingstate.CarUxRestrictions; 23 import android.car.drivingstate.CarUxRestrictionsManager; 24 import android.content.ComponentName; 25 import android.content.Intent; 26 import android.content.ServiceConnection; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageManager; 29 import android.os.Bundle; 30 import android.os.IBinder; 31 import android.text.TextUtils; 32 import android.util.Log; 33 import android.view.View; 34 import android.widget.Button; 35 import android.widget.TextView; 36 37 import com.android.car.CarLog; 38 import com.android.car.R; 39 40 /** 41 * Default activity that will be launched when the current foreground activity is not allowed. 42 * Additional information on blocked Activity will be passed as extra in Intent 43 * via {@link #INTENT_KEY_BLOCKED_ACTIVITY} key. 44 */ 45 public class ActivityBlockingActivity extends Activity { 46 public static final String INTENT_KEY_BLOCKED_ACTIVITY = "blocked_activity"; 47 public static final String EXTRA_BLOCKED_TASK = "blocked_task"; 48 49 private static final int INVALID_TASK_ID = -1; 50 51 private Car mCar; 52 private CarUxRestrictionsManager mUxRManager; 53 54 private TextView mBlockedTitle; 55 private Button mExitButton; 56 // Exiting depends on Car connection, which might not be available at the time exit was 57 // requested (e.g. user presses Exit Button). In that case, we record exiting was requested, and 58 // Car connection will perform exiting once it is established. 59 private boolean mExitRequested; 60 private int mBlockedTaskId; 61 62 @Override 63 protected void onCreate(Bundle savedInstanceState) { 64 super.onCreate(savedInstanceState); 65 setContentView(R.layout.activity_blocking); 66 67 mBlockedTitle = findViewById(R.id.activity_blocked_title); 68 mExitButton = findViewById(R.id.exit); 69 mExitButton.setOnClickListener(v -> handleFinish()); 70 71 // Listen to the CarUxRestrictions so this blocking activity can be dismissed when the 72 // restrictions are lifted. 73 mCar = Car.createCar(this, new ServiceConnection() { 74 @Override 75 public void onServiceConnected(ComponentName name, IBinder service) { 76 try { 77 if (mExitRequested) { 78 handleFinish(); 79 } 80 mUxRManager = (CarUxRestrictionsManager) mCar.getCarManager( 81 Car.CAR_UX_RESTRICTION_SERVICE); 82 // This activity would have been launched only in a restricted state. 83 // But ensuring when the service connection is established, that we are still 84 // in a restricted state. 85 handleUxRChange(mUxRManager.getCurrentCarUxRestrictions()); 86 mUxRManager.registerListener(ActivityBlockingActivity.this::handleUxRChange); 87 } catch (CarNotConnectedException e) { 88 Log.e(CarLog.TAG_AM, "Failed to get CarUxRestrictionsManager", e); 89 } 90 } 91 92 @Override 93 public void onServiceDisconnected(ComponentName name) { 94 finish(); 95 mUxRManager = null; 96 } 97 }); 98 mCar.connect(); 99 } 100 101 @Override 102 protected void onResume() { 103 super.onResume(); 104 105 // Display message about the current blocked activity, and optionally show an exit button 106 // to restart the blocked task (stack of activities) if its root activity is DO. 107 108 // blockedActivity is expected to be always passed in as the topmost activity of task. 109 String blockedActivity = getIntent().getStringExtra(INTENT_KEY_BLOCKED_ACTIVITY); 110 mBlockedTitle.setText(getString(R.string.activity_blocked_string, 111 findHumanReadableLabel(blockedActivity))); 112 if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) { 113 Log.d(CarLog.TAG_AM, "Blocking activity " + blockedActivity); 114 } 115 116 // taskId is available as extra if the task can be restarted. 117 mBlockedTaskId = getIntent().getIntExtra(EXTRA_BLOCKED_TASK, INVALID_TASK_ID); 118 119 mExitButton.setVisibility(mBlockedTaskId == INVALID_TASK_ID ? View.GONE : View.VISIBLE); 120 if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG) && mBlockedTaskId == INVALID_TASK_ID) { 121 Log.d(CarLog.TAG_AM, "Blocked task ID is not available. Hiding exit button."); 122 } 123 } 124 125 @Override 126 protected void onNewIntent(Intent intent) { 127 super.onNewIntent(intent); 128 setIntent(intent); 129 } 130 131 @Override 132 protected void onDestroy() { 133 super.onDestroy(); 134 if (mCar.isConnected() && mUxRManager != null) { 135 try { 136 mUxRManager.unregisterListener(); 137 } catch (CarNotConnectedException e) { 138 Log.e(CarLog.TAG_AM, "Cannot unregisterListener", e); 139 } 140 mUxRManager = null; 141 mCar.disconnect(); 142 } 143 } 144 145 // If no distraction optimization is required in the new restrictions, then dismiss the 146 // blocking activity (self). 147 private void handleUxRChange(CarUxRestrictions restrictions) { 148 if (restrictions == null) { 149 return; 150 } 151 if (!restrictions.isRequiresDistractionOptimization()) { 152 finish(); 153 } 154 } 155 156 /** 157 * Returns a human-readable string for {@code flattenComponentName}. 158 * 159 * <p>It first attempts to return the application label for this activity. If that fails, 160 * it will return the last part in the activity name. 161 */ 162 private String findHumanReadableLabel(String flattenComponentName) { 163 ComponentName componentName = ComponentName.unflattenFromString(flattenComponentName); 164 String label = null; 165 // Attempt to find application label. 166 try { 167 ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo( 168 componentName.getPackageName(), 0); 169 CharSequence appLabel = getPackageManager().getApplicationLabel(applicationInfo); 170 if (appLabel != null) { 171 label = appLabel.toString(); 172 } 173 } catch (PackageManager.NameNotFoundException e) { 174 if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) { 175 Log.i(CarLog.TAG_AM, "Could not find package for component name " 176 + componentName.toString()); 177 } 178 } 179 if (TextUtils.isEmpty(label)) { 180 label = componentName.getClass().getSimpleName(); 181 } 182 return label; 183 } 184 185 private void handleFinish() { 186 if (!mCar.isConnected()) { 187 mExitRequested = true; 188 return; 189 } 190 if (isFinishing()) { 191 return; 192 } 193 194 // Lock on self (assuming single instance) to avoid restarting the same task twice. 195 synchronized (this) { 196 try { 197 CarPackageManager carPm = (CarPackageManager) 198 mCar.getCarManager(Car.PACKAGE_SERVICE); 199 carPm.restartTask(mBlockedTaskId); 200 } catch (CarNotConnectedException e) { 201 // We should never be here since Car connection is already checked. 202 Log.e(CarLog.TAG_AM, "Car connection is not available.", e); 203 return; 204 } 205 finish(); 206 } 207 } 208 } 209