Final improvements on CarProvision app:
- Listen to Car UX restrictions so it exits if the car moves or
gear changes to rear.
- Sets the proper car-related Settings.
- Don't disable the DeviceOwner button if the DPC app is not
available (so it can be installed through adb)
- Exit right away on headless system user.
- Removed "Cancel Setup" button.
Test: m -j CarProvision && adb sync && \
adb shell am force-stop com.android.car.provision && \
adb shell pm enable --user cur com.android.car.provision && \
adb shell pm enable --user cur com.android.car.provision/.DefaultActivity && \
adb shell am start -a android.intent.action.MAIN \
-c android.intent.category.HOME -c android.intent.category.DEFAULT \
-c android.intent.category.SETUP_WIZARD
Fixes: 170143095
Bug: 171066617
Change-Id: I8147676d66f207a832d549b23d5f0394b93904f0
diff --git a/Android.bp b/Android.bp
index fd42623..4e2c402 100644
--- a/Android.bp
+++ b/Android.bp
@@ -15,6 +15,12 @@
android_app {
name: "CarProvision",
srcs: ["**/*.java"],
+ libs: [
+ "android.car-stubs",
+ ],
+ static_libs: [
+ "car-setup-wizard-lib",
+ ],
platform_apis: true,
system_ext_specific: true,
certificate: "platform",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 69d3ba5..fc9507d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -26,6 +26,12 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <!-- To listen to UX restriction events -->
+ <uses-permission android:name="android.car.permission.CAR_POWERTRAIN"/>
+
+ <!-- To show the exited setup notification -->
+ <uses-permission android:name="android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"/>
+
<application android:label="@string/app_name">
<activity android:name="DefaultActivity"
diff --git a/res/drawable-hdpi/car_ic_mode.png b/res/drawable-hdpi/car_ic_mode.png
new file mode 100644
index 0000000..a8f719f
--- /dev/null
+++ b/res/drawable-hdpi/car_ic_mode.png
Binary files differ
diff --git a/res/layout/default_activity.xml b/res/layout/default_activity.xml
index 0262dce..3850c22 100644
--- a/res/layout/default_activity.xml
+++ b/res/layout/default_activity.xml
@@ -39,7 +39,7 @@
android:id="@+id/error_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- </LinearLayout>
+ </LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -51,11 +51,6 @@
android:text="@string/set_do"
android:enabled="false"/>
<Button
- android:id="@+id/cancel_setup"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/cancel_setup"/>
- <Button
android:id="@+id/finish_setup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1e34891..24e8a7c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -18,11 +18,13 @@
<string name="app_name" translatable="false">Reference Setup Wizard for Cars</string>
<string name="description" translatable="false">This app is used to emulate a Setup Wizard.\n
\n
- It is integrated with car-specific features (such as deferred setup and user notice service)
+ It\'s integrated with car-specific features (such as exiting setup when car moves)
plus a UI where the user can emulate setup-time options like provisioning a Device Owner.
</string>
<string name="error_prompt" translatable="false">Error:</string>
- <string name="cancel_setup" translatable="false">Cancel Setup</string>
<string name="finish_setup" translatable="false">Finish Setup</string>
<string name="set_do" translatable="false">Set Device Owner</string>
+ <string name="exited_setup_title" translatable="false">Setup Exited</string>
+ <string name="exited_setup_content" translatable="false">Don\'t worry, this is just a friendly
+ FYI, there\'s nothing to do, you are good to go.\nHave fun!</string>
</resources>
diff --git a/src/com/android/car/provision/DefaultActivity.java b/src/com/android/car/provision/DefaultActivity.java
index 8486029..6dd7bdb 100644
--- a/src/com/android/car/provision/DefaultActivity.java
+++ b/src/com/android/car/provision/DefaultActivity.java
@@ -17,19 +17,28 @@
package com.android.car.provision;
import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
+import com.android.car.setupwizardlib.util.CarDrivingStateMonitor;
+
import java.util.HashMap;
import java.util.Map;
@@ -40,19 +49,26 @@
*
* <ul>
* <li>Shows UI where user can confirm setup.
- * <li>Listen to UX restriction events, so it defers setup when the car moves.
+ * <li>Listen to UX restriction events, so it exits setup when the car moves.
* <li>Add option to setup DeviceOwner mode.
* <li>Sets car-specific properties.
* </ul>
*/
public final class DefaultActivity extends Activity {
- // TODO(b/170957342): implement the features mentioned above
+ static final String TAG = "CarProvision";
- private static final String TAG = "CarProvision";
+ // TODO(b/171066617): copied from android.car.settings.CarSettings, as they're hidden
+ private static final String KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER =
+ "android.car.ENABLE_INITIAL_NOTICE_SCREEN_TO_USER";
+ private static final String KEY_SETUP_WIZARD_IN_PROGRESS =
+ "android.car.SETUP_WIZARD_IN_PROGRESS";
private static final int REQUEST_CODE_SET_DO = 42;
+ private static final int NOTIFICATION_ID = 108;
+ private static final String IMPORTANCE_DEFAULT_ID = "importance_default";
+
private static final Map<String, String> sSupportedDpcApps = new HashMap<>(1);
static {
@@ -61,27 +77,70 @@
"com.afwsamples.testdpc.SetupManagementLaunchActivity");
}
+ private CarDrivingStateMonitor mCarDrivingStateMonitor;
+
private TextView mErrorsTextView;
- private Button mCancelSetupButton;
private Button mFinishSetupButton;
private Button mDoProvisioningButton;
+ private final BroadcastReceiver mDrivingStateExitReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive(): " + intent);
+ exitSetup();
+ }
+ };
+
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- Log.i(TAG, "onCreate() for user " + getUserId());
+ int userId = getUserId();
+ Log.i(TAG, "onCreate() for user " + userId + " Intent: " + getIntent());
+
+ if (userId == UserHandle.USER_SYSTEM && UserManager.isHeadlessSystemUserMode()) {
+ finishSetup();
+ return;
+ }
+
+ setCarSetupInProgress(true);
setContentView(R.layout.default_activity);
mErrorsTextView = findViewById(R.id.error_message);
- mCancelSetupButton = findViewById(R.id.cancel_setup);
mFinishSetupButton = findViewById(R.id.finish_setup);
mDoProvisioningButton = findViewById(R.id.do_provisioning);
- mCancelSetupButton.setOnClickListener((v) -> cancelSetup());
mFinishSetupButton.setOnClickListener((v) -> finishSetup());
setDoProvisioning();
+ startMonitor();
+ }
+
+ private void startMonitor() {
+ registerReceiver(mDrivingStateExitReceiver,
+ new IntentFilter(CarDrivingStateMonitor.EXIT_BROADCAST_ACTION));
+
+ mCarDrivingStateMonitor = CarDrivingStateMonitor.get(this);
+ mCarDrivingStateMonitor.startMonitor();
+ }
+
+ @Override
+ public void finish() {
+ Log.i(TAG, "finish() for user " + getUserId());
+
+ stopMonitor();
+
+ super.finish();
+ };
+
+ private void stopMonitor() {
+ if (mDrivingStateExitReceiver != null) {
+ unregisterReceiver(mDrivingStateExitReceiver);
+ }
+
+ if (mCarDrivingStateMonitor != null) {
+ mCarDrivingStateMonitor.stopMonitor();
+ }
}
private void setDoProvisioning() {
@@ -98,13 +157,6 @@
return;
}
- // TODO(b/170143095): populate UI with supported options instead
- String dpcApp = sSupportedDpcApps.keySet().iterator().next();
- if (!checkDpcAppExists(dpcApp)) {
- Log.i(TAG, "Disabling DeviceOwner buttom because device does not have any DPC app");
- return;
- }
-
mDoProvisioningButton.setEnabled(true);
mDoProvisioningButton.setOnClickListener((v) -> provisionDeviceOwner());
}
@@ -136,28 +188,59 @@
private void finishSetup() {
Log.i(TAG, "finishing setup for user " + getUserId());
+ provisionUserAndDevice();
+ disableSelfAndFinish();
+ }
+
+ private void provisionUserAndDevice() {
+ Log.d(TAG, "setting Settings properties");
// Add a persistent setting to allow other apps to know the device has been provisioned.
Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);
Settings.Secure.putInt(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 1);
- // TODO(b/170143095): set android.car.SETUP_WIZARD_IN_PROGRESS and
- // android.car.ENABLE_INITIAL_NOTICE_SCREEN_TO_USER as well
-
- // TODO(b/170143095): listen to driving safety restriction events
-
- disableSelf();
-
- // Terminate the activity.
- finish();
+ // Set car-specific properties
+ setCarSetupInProgress(false);
+ Settings.Secure.putInt(getContentResolver(), KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER, 0);
}
- private void cancelSetup() {
- Log.i(TAG, "cancelling setup for user " + getUserId());
- finish();
+ private void setCarSetupInProgress(boolean inProgress) {
+ Settings.Secure.putInt(getContentResolver(), KEY_SETUP_WIZARD_IN_PROGRESS,
+ inProgress ? 1 : 0);
+ }
+
+ private void exitSetup() {
+ Log.d(TAG, "exitSetup()");
+ provisionUserAndDevice();
+ notifySetupExited();
+ disableSelfAndFinish();
+ }
+
+ private void notifySetupExited() {
+ Log.d(TAG, "Sending exited setup notification");
+
+ NotificationManager notificationMgr = getSystemService(NotificationManager.class);
+ notificationMgr.createNotificationChannel(new NotificationChannel(
+ IMPORTANCE_DEFAULT_ID, "Importance Default",
+ NotificationManager.IMPORTANCE_DEFAULT));
+ Notification notification = new Notification
+ .Builder(this, IMPORTANCE_DEFAULT_ID)
+ .setContentTitle(getString(R.string.exited_setup_title))
+ .setContentText(getString(R.string.exited_setup_content))
+ .setCategory(Notification.CATEGORY_CAR_INFORMATION)
+ .setSmallIcon(R.drawable.car_ic_mode)
+ .build();
+ notificationMgr.notify(NOTIFICATION_ID, notification);
}
private void provisionDeviceOwner() {
// TODO(b/170957342): add a UI with multiple options once AAOS provides a CarTestDPC app.
+ String dpcApp = sSupportedDpcApps.keySet().iterator().next();
+ if (!checkDpcAppExists(dpcApp)) {
+ showErrorMessage("Cannot setup DeviceOwner because " + dpcApp + " is not available.\n"
+ + "Make sure it's installed for both user 0 and user " + getUserId());
+ return;
+ }
+
Intent intent = new Intent();
Map.Entry<String, String> dpc = sSupportedDpcApps.entrySet().iterator().next();
intent.setComponent(new ComponentName(dpc.getKey(), dpc.getValue()));
@@ -166,13 +249,15 @@
startActivityForResult(intent, REQUEST_CODE_SET_DO);
}
- private void disableSelf() {
+ private void disableSelfAndFinish() {
// Remove this activity from the package manager.
PackageManager pm = getPackageManager();
ComponentName name = new ComponentName(this, DefaultActivity.class);
Log.i(TAG, "Disabling itself (" + name + ") for user " + getUserId());
pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
+
+ finish();
}
@Override
@@ -189,7 +274,7 @@
return;
}
Log.i(TAG, "Device owner mode provisioned, nothing left to do...");
- disableSelf();
+ disableSelfAndFinish();
};
private static String resultCodeToString(int resultCode) {