Home | History | Annotate | Download | only in pm
      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