Merge "DO NOT MERGE Allow static overlays of the car/libs used by Settings." into pi-car-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7479f10..47c8256 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -262,6 +262,15 @@
</intent-filter>
</activity>
+ <activity android:name=".bluetooth.BluetoothRequestPermissionActivity"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name="android.bluetooth.adapter.action.REQUEST_ENABLE" />
+ <action android:name="android.bluetooth.adapter.action.REQUEST_DISABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".accounts.AddAccountActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:configChanges="orientation|keyboardHidden|screenSize"/>
diff --git a/res/drawable/ic_settings_units.xml b/res/drawable/ic_settings_units.xml
new file mode 100644
index 0000000..22142c8
--- /dev/null
+++ b/res/drawable/ic_settings_units.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/icon_size"
+ android:height="@dimen/icon_size"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0">
+ <path
+ android:fillColor="?attr/iconColor"
+ android:pathData="M17.66,17.66l-1.06,1.06l-0.71-0.71l1.06-1.06l-1.94-1.94l-1.06,1.06l-0.71-0.71l1.06-1.06l-1.94-1.94l-1.06,1.06 l-0.71-0.71l1.06-1.06L9.7,9.7l-1.06,1.06l-0.71-0.71l1.06-1.06L7.05,7.05L5.99,8.11L5.28,7.4l1.06-1.06L4,4v14c0,1.1,0.9,2,2,2 h14L17.66,17.66z M7,17v-5.76L12.76,17H7z"/>
+</vector>
diff --git a/res/values/preference_keys.xml b/res/values/preference_keys.xml
index fc4f338..fc9c2d7 100644
--- a/res/values/preference_keys.xml
+++ b/res/values/preference_keys.xml
@@ -265,6 +265,14 @@
<string name="pk_default_alarm" translatable="false">default_alarm</string>
<string name="pk_sounds_extra_settings" translatable="false">sounds_extra_settings</string>
+ <!-- Units Settings -->
+ <string name="pk_units_settings_entry" translatable="false">units_settings_entry</string>
+ <string name="pk_units_distance_and_volume" translatable="false">units_distance_and_volume</string>
+ <string name="pk_units_speed">units_speed</string>
+ <string name="pk_units_fuel_consumption">units_fuel_consumption</string>
+ <string name="pk_units_temperature">units_temperature</string>
+ <string name="pk_units_pressure">units_pressure</string>
+
<!-- Location Settings -->
<string name="pk_location_recent_requests_entry" translatable="false">
location_recent_requests_entry
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6e5d4a1..1805e8d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -293,6 +293,18 @@
<string name="bluetooth_profiles">Profiles</string>
<!-- Title for BT error dialogs. -->
<string name="bluetooth_error_title"></string>
+ <!-- Strings for msg to display to user while bluetooth is turning on [CHAR LIMIT=60] -->
+ <string name="bluetooth_turning_on">"Turning Bluetooth on\u2026"</string>
+ <!-- Strings for msg to display to user while bluetooth is turning off [CHAR LIMIT=60] -->
+ <string name="bluetooth_turning_off">"Turning Bluetooth off\u2026"</string>
+ <!-- This string asks the user whether or not to allow an app to enable bluetooth. [CHAR LIMIT=100] -->
+ <string name="bluetooth_ask_enablement"><xliff:g id="app_name" example="FancyApp">%1$s</xliff:g> wants to turn on Bluetooth</string>
+ <!-- This string asks the user whether or not to allow an app to disable bluetooth. [CHAR LIMIT=100] -->
+ <string name="bluetooth_ask_disablement"><xliff:g id="app_name" example="FancyApp">%1$s</xliff:g> wants to turn off Bluetooth</string>
+ <!-- This string asks the user whether or not to allow an app to enable bluetooth. [CHAR LIMIT=100] -->
+ <string name="bluetooth_ask_enablement_no_name">An app wants to turn on Bluetooth</string>
+ <!-- This string asks the user whether or not to allow an app to disable bluetooth. [CHAR LIMIT=100] -->
+ <string name="bluetooth_ask_disablement_no_name">An app wants to turn off Bluetooth</string>
<!-- Bluetooth pairing --><skip/>
<!-- Notification ticker text (shown in the status bar) when a Bluetooth device wants to pair with us -->
@@ -369,6 +381,125 @@
<!-- Sound settings screen, title for the option defining the default alarm sound. [CHAR LIMIT=30] -->
<string name="alarm_ringtone_title">Default alarm sound</string>
+ <!-- Units Settings -->
+ <!-- Units Settings title [CHAR LIMIT=10] -->
+ <string name="units_settings">Units</string>
+ <string name="units_distance_and_volume_title">Distance and volume</string>
+ <string name="units_speed_title">Speed</string>
+ <string name="units_fuel_consumption_title">Fuel consumption</string>
+ <string name="units_battery_consumption_title">Energy consumption</string>
+ <string name="units_temperature_title">Temperature</string>
+ <string name="units_pressure_title">Pressure</string>
+
+ <!-- Meter per Second Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_meter_per_sec">Meter per Second</string>
+ <!-- Revolutions per Minute Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_rpm">Revolutions per Minute</string>
+ <!-- Hertz Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_hertz">Hertz</string>
+ <!-- Percentile Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_percentile">Percentile</string>
+ <!-- Millimeter Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_millimeter">Millimeter</string>
+ <!-- Meter Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_meter">Meter</string>
+ <!-- Kilometer Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_kilometer">Kilometer</string>
+ <!-- Mile Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_mile">Mile</string>
+ <!-- Celsius Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_celsius">Celsius</string>
+ <!-- Fahrenheit Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_fahrenheit">Fahrenheit</string>
+ <!-- Kelvin Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_kelvin">Kelvin</string>
+ <!-- Milliliter Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_milliliter">Milliliter</string>
+ <!-- Liter Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_liter">Liter</string>
+ <!-- Gallon Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_us_gallon">Gallon</string>
+ <!-- Imperial Gallon Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_imperial_gallon">Imperial Gallon</string>
+ <!-- Nanosecond Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_nano_secs">Nanosecond</string>
+ <!-- Second Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_secs">Second</string>
+ <!-- Year Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_year">Year</string>
+ <!-- Kilopascal Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_kilopascal">Kilopascal</string>
+ <!-- Watt Hour Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_watt_hour">Watt Hour</string>
+ <!-- Milliampere Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_milliampere">Milliampere</string>
+ <!-- Millivolt Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_millivolt">Millivolt</string>
+ <!-- Milliwatt Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_milliwatts">Milliwatt</string>
+ <!-- Ampere hour Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_ampere_hour">Ampere hour</string>
+ <!-- Kilowatt hour Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_kilowatt_hour">Kilowatt hour</string>
+ <!-- Pounds per square inch Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_psi">Pounds per square inch</string>
+ <!-- Miles per hour Unit as it is pronounced [CHAR LIMIT = 50]-->
+ <string name="units_unit_name_miles_per_hour">Miles per hour</string>
+ <!-- Kilometers per hour Unit as it is pronounced [CHAR LIMIT = 50]-->
+ <string name="units_unit_name_kilometers_per_hour">Kilometers per hour</string>
+ <!-- Bar Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_bar">Bar</string>
+ <!-- Degree Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_degrees">Degree</string>
+ <!-- Miles per Kilowatt Unit as it is pronounced [CHAR LIMIT=80]-->
+ <string name="units_unit_name_kilowatt_per_hundred_miles">Kilowatt per hundred Miles</string>
+ <!-- Kilometers per Kilowatt Unit as it is pronounced [CHAR LIMIT=80]-->
+ <string name="units_unit_name_kilowatt_per_hundred_kilometers">Kilowatt per hundred Kilometers</string>
+ <!--Miles per gallon Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_miles_per_gallon_us">Miles per Gallon (US)</string>
+ <!--Miles per gallon Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_miles_per_gallon_uk">Miles per Gallon (UK)</string>
+ <!--Kilometers per liter Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_kilometers_per_liter">Kilometers per Liter</string>
+ <!--Kilometers per liter Unit as it is pronounced [CHAR LIMIT=50]-->
+ <string name="units_unit_name_liter_per_hundred_kilometers">Liters per hundred Kilometers</string>
+
+ <!-- Units in abbreviated form -->
+ <string name="units_unit_abbreviation_meter_per_sec" translatable="false">m/s</string>
+ <string name="units_unit_abbreviation_rpm" translatable="false">RPM</string>
+ <string name="units_unit_abbreviation_hertz" translatable="false">Hz</string>
+ <string name="units_unit_abbreviation_percentile" translatable="false">%</string>
+ <string name="units_unit_abbreviation_millimeter" translatable="false">mm</string>
+ <string name="units_unit_abbreviation_meter" translatable="false">m</string>
+ <string name="units_unit_abbreviation_kilometer" translatable="false">km</string>
+ <string name="units_unit_abbreviation_mile" translatable="false">mi</string>
+ <string name="units_unit_abbreviation_celsius" translatable="false">°C</string>
+ <string name="units_unit_abbreviation_fahrenheit" translatable="false">°F</string>
+ <string name="units_unit_abbreviation_kelvin" translatable="false">K</string>
+ <string name="units_unit_abbreviation_milliliter" translatable="false">ml</string>
+ <string name="units_unit_abbreviation_liter" translatable="false">L</string>
+ <string name="units_unit_abbreviation_us_gallon" translatable="false">gal</string>
+ <string name="units_unit_abbreviation_imperial_gallon" translatable="false">IG</string>
+ <string name="units_unit_abbreviation_nano_secs" translatable="false">ns</string>
+ <string name="units_unit_abbreviation_secs" translatable="false">s</string>
+ <string name="units_unit_abbreviation_year" translatable="false">y</string>
+ <string name="units_unit_abbreviation_kilopascal" translatable="false">kPa</string>
+ <string name="units_unit_abbreviation_watt_hour" translatable="false">Wh</string>
+ <string name="units_unit_abbreviation_milliampere" translatable="false">mA</string>
+ <string name="units_unit_abbreviation_millivolt" translatable="false">mV</string>
+ <string name="units_unit_abbreviation_milliwatts" translatable="false">mW</string>
+ <string name="units_unit_abbreviation_ampere_hour" translatable="false">Ah</string>
+ <string name="units_unit_abbreviation_kilowatt_hour" translatable="false">kWh</string>
+ <string name="units_unit_abbreviation_psi" translatable="false">PSI</string>
+ <string name="units_unit_abbreviation_bar" translatable="false">bar</string>
+ <string name="units_unit_abbreviation_degrees" translatable="false">°</string>
+ <string name="units_unit_abbreviation_miles_per_hour" translatable="false">mph</string>
+ <string name="units_unit_abbreviation_kilometers_per_hour" translatable="false">km/h</string>
+ <string name="units_unit_abbreviation_miles_per_gallon_us" translatable="false">mpg (US)</string>
+ <string name="units_unit_abbreviation_miles_per_gallon_uk" translatable="false">mpg (UK)</string>
+ <string name="units_unit_abbreviation_kilometers_per_liter" translatable="false">km/L</string>
+ <string name="units_unit_abbreviation_liters_per_hundred_kilometers" translatable="false">L/100km</string>
+
<!-- applications --><skip/>
<!-- Apps and notifications settings title, on main settings screen. If clicked, the user is taken to a settings screen full of apps and notifications settings [CHAR LIMIT=40] -->
<string name="apps_and_notifications_settings">Apps & notifications</string>
diff --git a/res/xml/homepage_fragment.xml b/res/xml/homepage_fragment.xml
index 75944e5..fa343f6 100644
--- a/res/xml/homepage_fragment.xml
+++ b/res/xml/homepage_fragment.xml
@@ -37,6 +37,11 @@
android:title="@string/sound_settings"
settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
<Preference
+ android:fragment="com.android.car.settings.units.UnitsSettingsFragment"
+ android:icon="@drawable/ic_settings_units"
+ android:key="@string/pk_units_settings_entry"
+ android:title="@string/units_settings"/>
+ <Preference
android:fragment="com.android.car.settings.network.NetworkAndInternetFragment"
android:icon="@drawable/ic_settings_wifi"
android:key="@string/pk_network_and_internet_entry"
diff --git a/res/xml/units_fragment.xml b/res/xml/units_fragment.xml
new file mode 100644
index 0000000..ee17049
--- /dev/null
+++ b/res/xml/units_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/units_settings" >
+ <ListPreference
+ android:key="@string/pk_units_temperature"
+ android:title="@string/units_temperature_title"
+ android:dialogTitle="@string/units_temperature_title"
+ settings:controller="com.android.car.settings.units.UnitsTemperaturePreferenceController"/>
+</PreferenceScreen>
diff --git a/src/com/android/car/settings/bluetooth/BluetoothRequestPermissionActivity.java b/src/com/android/car/settings/bluetooth/BluetoothRequestPermissionActivity.java
new file mode 100644
index 0000000..c285c3d
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothRequestPermissionActivity.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.bluetooth;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.text.TextUtils;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+/**
+ * Code drop from {@link com.android.settings.bluetooth.RequestPermissionActivity}.
+ *
+ * This {@link Activity} handles requests to toggle Bluetooth by collecting user
+ * consent and waiting until the state change is completed.
+ */
+public class BluetoothRequestPermissionActivity extends Activity implements
+ DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ private static final Logger LOG = new Logger(BluetoothRequestPermissionActivity.class);
+ private static final int REQUEST_UNKNOWN = 0;
+ private static final int REQUEST_ENABLE = 1;
+ private static final int REQUEST_DISABLE = 2;
+ private int mRequest;
+ @NonNull
+ private CharSequence mAppLabel;
+ private LocalBluetoothAdapter mLocalBluetoothAdapter;
+ private LocalBluetoothManager mLocalBluetoothManager;
+ private AlertDialog mDialog;
+ private StateChangeReceiver mReceiver;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setResult(Activity.RESULT_CANCELED);
+
+ mRequest = parseIntent();
+ if (mRequest == REQUEST_UNKNOWN) {
+ finish();
+ return;
+ }
+
+ mReceiver = new StateChangeReceiver();
+
+ int btState = mLocalBluetoothAdapter.getState();
+ switch (mRequest) {
+ case REQUEST_DISABLE:
+ switch (btState) {
+ case BluetoothAdapter.STATE_OFF:
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ proceedAndFinish();
+ break;
+
+ case BluetoothAdapter.STATE_ON:
+ case BluetoothAdapter.STATE_TURNING_ON:
+ createDecisionDialog();
+ break;
+
+ default:
+ LOG.e("Unknown adapter state: " + btState);
+ finish();
+ break;
+ }
+ break;
+ case REQUEST_ENABLE:
+ switch (btState) {
+ case BluetoothAdapter.STATE_OFF:
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ createDecisionDialog();
+ break;
+
+ case BluetoothAdapter.STATE_ON:
+ proceedAndFinish();
+ break;
+ default:
+ LOG.e("Unknown adapter state: " + btState);
+ finish();
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mReceiver.register();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mReceiver.unregister();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ proceedAndFinish();
+ break;
+
+ case DialogInterface.BUTTON_NEGATIVE:
+ onCancel(/* dialog = */ null);
+ break;
+ }
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+
+ private void createInterimDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(/* context = */ this);
+
+ switch (mRequest) {
+ case REQUEST_ENABLE:
+ builder.setMessage(getString(R.string.bluetooth_turning_on));
+ break;
+ default:
+ builder.setMessage(getString(R.string.bluetooth_turning_off));
+ break;
+ }
+ builder.setCancelable(false).setOnCancelListener(/* listener = */ this);
+
+ mDialog = builder.create();
+ mDialog.show();
+ }
+
+ private void createDecisionDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(/* context= */ this);
+ switch (mRequest) {
+ case REQUEST_ENABLE:
+ builder.setMessage(
+ mAppLabel != null ? getString(R.string.bluetooth_ask_enablement,
+ mAppLabel)
+ : getString(R.string.bluetooth_ask_enablement_no_name));
+ break;
+
+ case REQUEST_DISABLE: {
+ builder.setMessage(
+ mAppLabel != null ? getString(R.string.bluetooth_ask_disablement, mAppLabel)
+ : getString(R.string.bluetooth_ask_disablement_no_name));
+ break;
+ }
+ }
+
+ builder.setPositiveButton(R.string.allow, this::decisionDialogPositiveButtonListener)
+ .setNegativeButton(R.string.deny, (dialog, which) -> onCancel(/* dialog = */ null))
+ .setOnCancelListener(/* listener = */ this);
+
+ mDialog = builder.create();
+ mDialog.show();
+ }
+
+ private void decisionDialogPositiveButtonListener(DialogInterface dialog, int which) {
+ dialog.dismiss();
+
+ if (!hasUserRestriction()) {
+ switch (mRequest) {
+ case REQUEST_ENABLE:
+ if (mLocalBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
+ proceedAndFinish();
+ } else {
+ // If BT is not up yet, show "Turning on Bluetooth..."
+ createInterimDialog();
+ }
+ break;
+
+ case REQUEST_DISABLE:
+ if (mLocalBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
+ proceedAndFinish();
+ } else {
+ // If BT is not up yet, show "Turning off Bluetooth..."
+ createInterimDialog();
+ }
+ break;
+
+ default:
+ finish();
+ break;
+ }
+ }
+ }
+
+ private boolean hasUserRestriction() {
+ switch (mRequest) {
+ case REQUEST_ENABLE:
+ UserManager userManager = getSystemService(UserManager.class);
+ if (userManager.hasUserRestriction(UserManager.DISALLOW_BLUETOOTH)) {
+ // If Bluetooth is disallowed, don't try to enable it, show policy
+ // transparency
+ // message instead.
+ DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
+ Intent intent = dpm.createAdminSupportIntent(
+ UserManager.DISALLOW_BLUETOOTH);
+ if (intent != null) {
+ startActivity(intent);
+ }
+
+ return true;
+ } else {
+ mLocalBluetoothAdapter.enable();
+ }
+ break;
+
+ case REQUEST_DISABLE: {
+ mLocalBluetoothAdapter.disable();
+ }
+ break;
+ }
+
+ return false;
+ }
+
+ private void proceedAndFinish() {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ finish();
+ }
+
+ private int parseIntent() {
+ int request = REQUEST_UNKNOWN;
+ Intent intent = getIntent();
+ if (intent == null) {
+ return REQUEST_UNKNOWN;
+ }
+
+ switch (intent.getAction()) {
+ case BluetoothAdapter.ACTION_REQUEST_ENABLE:
+ request = REQUEST_ENABLE;
+ break;
+ case BluetoothAdapter.ACTION_REQUEST_DISABLE:
+ request = REQUEST_DISABLE;
+ break;
+ default:
+ LOG.e("Error: this activity may be started only with intent "
+ + BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ return REQUEST_UNKNOWN;
+ }
+
+ String packageName = getCallingPackage();
+ if (TextUtils.isEmpty(packageName)) {
+ packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ }
+ if (!TextUtils.isEmpty(packageName)) {
+ try {
+ ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
+ packageName, 0);
+ mAppLabel = applicationInfo.loadLabel(getPackageManager());
+ } catch (PackageManager.NameNotFoundException e) {
+ LOG.e("Couldn't find app with package name " + packageName);
+ return REQUEST_UNKNOWN;
+ }
+ }
+
+ mLocalBluetoothManager = LocalBluetoothManager.getInstance(
+ getApplicationContext(), /* onInitCallback= */ null);
+ if (mLocalBluetoothManager == null) {
+ LOG.e("Bluetooth is not supported on this device");
+ return REQUEST_UNKNOWN;
+ }
+
+ mLocalBluetoothAdapter = mLocalBluetoothManager.getBluetoothAdapter();
+ if (mLocalBluetoothAdapter == null) {
+ LOG.e("Error: there's a problem starting Bluetooth");
+ return REQUEST_UNKNOWN;
+ }
+
+ return request;
+ }
+
+ private final class StateChangeReceiver extends BroadcastReceiver {
+ private static final long TOGGLE_TIMEOUT_MILLIS = 10000; // 10 sec
+
+ StateChangeReceiver() {
+ getWindow().getDecorView().postDelayed(() -> {
+ if (!isFinishing() && !isDestroyed()) {
+ onCancel(null);
+ }
+ }, TOGGLE_TIMEOUT_MILLIS);
+ }
+
+ public void register() {
+ registerReceiver(/* receiver= */ this,
+ new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
+ }
+
+ public void unregister() {
+ unregisterReceiver(/* receiver= */ this);
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ Activity activity = BluetoothRequestPermissionActivity.this;
+ if (intent == null) {
+ return;
+ }
+ int currentState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+ BluetoothDevice.ERROR);
+ switch (mRequest) {
+ case REQUEST_ENABLE:
+ if (currentState == BluetoothAdapter.STATE_ON) {
+ activity.setResult(Activity.RESULT_OK);
+ proceedAndFinish();
+ }
+ break;
+
+ case REQUEST_DISABLE:
+ if (currentState == BluetoothAdapter.STATE_OFF) {
+ activity.setResult(Activity.RESULT_OK);
+ proceedAndFinish();
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/com/android/car/settings/units/CarUnitsManager.java b/src/com/android/car/settings/units/CarUnitsManager.java
new file mode 100644
index 0000000..e8d8b69
--- /dev/null
+++ b/src/com/android/car/settings/units/CarUnitsManager.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.units;
+
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.VehiclePropertyIds;
+import android.car.VehicleUnit;
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.property.CarPropertyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.ArraySet;
+
+import com.android.car.settings.common.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Utility to read and write {@link Unit}-related properties in {@link CarPropertyManager}. */
+public class CarUnitsManager {
+ private static final Logger LOG = new Logger(CarUnitsManager.class);
+ private static final int AREA_ID = 0;
+
+ private final ServiceConnection mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ mCarPropertyManager =
+ (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
+ mCarServiceListener.handleServiceConnected(mCarPropertyManager);
+ } catch (CarNotConnectedException e) {
+ LOG.e("Car is not connected!", e);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mCarServiceListener.handleServiceDisconnected();
+ }
+ };
+
+ private Context mContext;
+ private Car mCar;
+ private CarPropertyManager mCarPropertyManager;
+ private OnCarServiceListener mCarServiceListener;
+
+ public CarUnitsManager(Context context) {
+ mContext = context;
+ mCar = Car.createCar(mContext, mServiceConnection);
+ }
+
+ /**
+ * Registers {@link OnCarServiceListener} as a Callback for when connection to {@link Car} has
+ * been established.
+ */
+ public void registerCarServiceListener(OnCarServiceListener listener) {
+ mCarServiceListener = listener;
+ }
+
+ /**
+ * Unregisters {@link OnCarServiceListener} as a Callback for when connection to {@link Car} has
+ * been terminated.
+ */
+ public void unregisterCarServiceListener() {
+ mCarServiceListener = null;
+ }
+
+ protected void connect() {
+ mCar.connect();
+ }
+
+ protected void disconnect() {
+ mCar.disconnect();
+ }
+
+ protected boolean isPropertyAvailable(int propertyId) {
+ Integer intProperty = null;
+
+ try {
+ intProperty = mCarPropertyManager.getIntProperty(propertyId, AREA_ID);
+ } catch (CarNotConnectedException e) {
+ LOG.e("Property is unavailable because Car is not connected.");
+ }
+
+ return intProperty != null && intProperty != VehicleUnit.SHOULD_NOT_USE;
+ }
+
+ protected Unit[] getUnitsSupportedByProperty(int propertyId) {
+ ArraySet<Integer> propertyIdSet = new ArraySet<Integer>();
+ propertyIdSet.add(propertyId);
+ List<CarPropertyConfig> configs = mCarPropertyManager.getPropertyList(propertyIdSet);
+ List<Integer> availableUnitsId = new ArrayList<Integer>();
+ List<Unit> units = new ArrayList<Unit>();
+
+ if (configs.get(0) == null) {
+ return null;
+ }
+
+ availableUnitsId = configs.get(0).getConfigArray();
+
+ Unit[] result = new Unit[availableUnitsId.size()];
+ for (int unitId : availableUnitsId) {
+ if (UnitsMap.MAP.get(unitId) != null) {
+ Unit unit = UnitsMap.MAP.get(unitId);
+ units.add(unit);
+ }
+ }
+ for (int i = 0; i < result.length; i++) {
+ int unitId = availableUnitsId.get(i);
+ if (UnitsMap.MAP.get(unitId) != null) {
+ Unit unit = UnitsMap.MAP.get(unitId);
+ result[i] = unit;
+ }
+ }
+ return result;
+ }
+
+ protected Unit getUnitUsedByProperty(int propertyId) {
+ try {
+ int unitId = mCarPropertyManager.getIntProperty(propertyId, AREA_ID);
+ if (UnitsMap.MAP.get(unitId) != null) {
+ return UnitsMap.MAP.get(unitId);
+ } else {
+ return null;
+ }
+ } catch (CarNotConnectedException e) {
+ LOG.e("CarPropertyManager cannot get property because Car is not connected.");
+ return null;
+ }
+ }
+
+ protected void setUnitUsedByProperty(int propertyId, int unitId) {
+ try {
+ mCarPropertyManager.setIntProperty(propertyId, AREA_ID, unitId);
+ } catch (CarNotConnectedException e) {
+ LOG.e("CarPropertyManager cannot set property because Car is not connected.");
+ }
+ }
+
+ /**
+ * Returns a boolean that indicates whether the unit is expressed in distance per volume (true)
+ * or volume per distance (false) for fuel consumption. Note that only distance over volume
+ * format is supported when Mile and Gallon (both US and UK) units are used.
+ */
+ protected boolean isDistanceOverVolume() {
+ try {
+ return mCarPropertyManager.getBooleanProperty(
+ VehiclePropertyIds.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME, AREA_ID);
+ } catch (CarNotConnectedException e) {
+ return true; // Defaults to True.
+ }
+ }
+
+ /** Defines callbacks that listen to {@link Car} service-related events. */
+ public interface OnCarServiceListener {
+ /**
+ * Callback to be run when {@link Car} service is connected and {@link
+ * CarPropertyManager} becomes available.
+ */
+ void handleServiceConnected(CarPropertyManager carPropertyManager);
+
+ /** Callback to be run when {@link Car} service is disconnected. */
+ void handleServiceDisconnected();
+ }
+}
diff --git a/src/com/android/car/settings/units/Unit.java b/src/com/android/car/settings/units/Unit.java
new file mode 100644
index 0000000..bc5e9b7
--- /dev/null
+++ b/src/com/android/car/settings/units/Unit.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.units;
+
+import android.annotation.StringRes;
+import android.car.VehicleUnit;
+
+/**
+ * Contains properties and methods associated with each unit defined in {@link
+ * android.car.VehicleUnit}.
+ */
+public class Unit {
+ private final int mId;
+ private final int mAbbreviationResId;
+ private final int mNameResId;
+
+ Unit(@VehicleUnit.Enum int unitId, @StringRes int unitAbbreviationResId,
+ @StringRes int unitNameResId) {
+ mId = unitId;
+ mAbbreviationResId = unitAbbreviationResId;
+ mNameResId = unitNameResId;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public int getAbbreviationResId() {
+ return mAbbreviationResId;
+ }
+
+ public int getNameResId() {
+ return mNameResId;
+ }
+}
diff --git a/src/com/android/car/settings/units/UnitsBasePreferenceController.java b/src/com/android/car/settings/units/UnitsBasePreferenceController.java
new file mode 100644
index 0000000..83567fd
--- /dev/null
+++ b/src/com/android/car/settings/units/UnitsBasePreferenceController.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.car.settings.units;
+
+import android.car.CarNotConnectedException;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.content.Context;
+
+import androidx.annotation.CallSuper;
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Shared business logic for preference controllers related to Units.
+ */
+public abstract class UnitsBasePreferenceController extends PreferenceController<ListPreference> {
+
+ @VisibleForTesting
+ protected final CarUnitsManager.OnCarServiceListener mOnCarServiceListener =
+ new CarUnitsManager.OnCarServiceListener() {
+ @Override
+ public void handleServiceConnected(CarPropertyManager carPropertyManager) {
+ try {
+ if (carPropertyManager != null) {
+ carPropertyManager.registerCallback(mCarPropertyEventCallback,
+ getPropertyId(), CarPropertyManager.SENSOR_RATE_ONCHANGE);
+ }
+ mSupportedUnits = mCarUnitsManager.getUnitsSupportedByProperty(
+ getPropertyId());
+ if (mSupportedUnits != null && mSupportedUnits.length > 0) {
+ // first element in the config array is the default Unit per VHAL spec.
+ mDefaultUnit = mSupportedUnits[0];
+ getPreference().setEntries(getEntriesOfSupportedUnits());
+ getPreference().setEntryValues(getIdsOfSupportedUnits());
+ getPreference().setValue(
+ Integer.toString(getUnitUsedByThisProperty().getId()));
+ refreshUi();
+ }
+
+ mIsCarUnitsManagerStarted = true;
+ } catch (CarNotConnectedException e) {
+ }
+ }
+
+ @Override
+ public void handleServiceDisconnected() {
+ mIsCarUnitsManagerStarted = false;
+ }
+ };
+
+ @VisibleForTesting
+ protected final CarPropertyManager.CarPropertyEventCallback mCarPropertyEventCallback =
+ new CarPropertyManager.CarPropertyEventCallback() {
+ @Override
+ public void onChangeEvent(CarPropertyValue value) {
+ if (value != null && value.getStatus() == CarPropertyValue.STATUS_AVAILABLE) {
+ mUnitBeingUsed = UnitsMap.MAP.get(value.getValue());
+ refreshUi();
+ }
+ }
+
+ @Override
+ public void onErrorEvent(int propId, int zone) {
+ }
+ };
+
+ private Unit[] mSupportedUnits;
+ private Unit mUnitBeingUsed;
+ private Unit mDefaultUnit;
+ private boolean mIsCarUnitsManagerStarted = false;
+ private CarUnitsManager mCarUnitsManager;
+
+ public UnitsBasePreferenceController(Context context, String preferenceKey,
+ FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+ super(context, preferenceKey, fragmentController, uxRestrictions);
+ }
+
+ @Override
+ @CallSuper
+ protected void onCreateInternal() {
+ super.onCreateInternal();
+ mCarUnitsManager = new CarUnitsManager(getContext());
+ mCarUnitsManager.connect();
+ mCarUnitsManager.registerCarServiceListener(mOnCarServiceListener);
+ }
+
+ @Override
+ @CallSuper
+ protected void onDestroyInternal() {
+ super.onDestroyInternal();
+ mCarUnitsManager.disconnect();
+ mCarUnitsManager.unregisterCarServiceListener();
+ }
+
+ @Override
+ @CallSuper
+ protected void updateState(ListPreference preference) {
+ if (mIsCarUnitsManagerStarted && mUnitBeingUsed != null) {
+ preference.setSummary(generateSummaryFromUnit(mUnitBeingUsed));
+ preference.setValue(Integer.toString(mUnitBeingUsed.getId()));
+ }
+ }
+
+ @Override
+ @CallSuper
+ public boolean handlePreferenceChanged(ListPreference preference, Object newValue) {
+ int unitId = Integer.parseInt((String) newValue);
+ mCarUnitsManager.setUnitUsedByProperty(getPropertyId(), unitId);
+ return true;
+ }
+
+ @Override
+ protected int getAvailabilityStatus() {
+ return mSupportedUnits != null && mSupportedUnits.length > 0
+ ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ protected abstract int getPropertyId();
+
+ protected String[] getEntriesOfSupportedUnits() {
+ String[] names = new String[mSupportedUnits.length];
+ for (int i = 0; i < names.length; i++) {
+ Unit unit = mSupportedUnits[i];
+ names[i] = generateEntryStringFromUnit(unit);
+ }
+ return names;
+ }
+
+ protected String generateSummaryFromUnit(Unit unit) {
+ return getContext().getString(unit.getAbbreviationResId());
+ }
+
+ protected String generateEntryStringFromUnit(Unit unit) {
+ return getContext().getString(unit.getAbbreviationResId()) + " - "
+ + getContext().getString(unit.getNameResId());
+ }
+
+ protected String[] getIdsOfSupportedUnits() {
+ String[] ids = new String[mSupportedUnits.length];
+ for (int i = 0; i < ids.length; i++) {
+ ids[i] = Integer.toString(mSupportedUnits[i].getId());
+ }
+ return ids;
+ }
+
+ protected CarUnitsManager getCarUnitsManager() {
+ return mCarUnitsManager;
+ }
+
+ private Unit getUnitUsedByThisProperty() {
+ Unit savedUnit = mCarUnitsManager.getUnitUsedByProperty(getPropertyId());
+ if (savedUnit == null) {
+ return mDefaultUnit;
+ }
+ return savedUnit;
+ }
+
+}
diff --git a/src/com/android/car/settings/units/UnitsMap.java b/src/com/android/car/settings/units/UnitsMap.java
new file mode 100644
index 0000000..12264de
--- /dev/null
+++ b/src/com/android/car/settings/units/UnitsMap.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.units;
+
+import android.car.VehicleUnit;
+
+import com.android.car.settings.R;
+
+import java.util.HashMap;
+
+/**
+ * Contains {@link Unit} instances for all units defined in {@link VehicleUnit}. This mapping is
+ * safe because OEMs cannot define their own VehicleUnit.
+ */
+public final class UnitsMap {
+ protected static final Unit METER_PER_SEC = new Unit(VehicleUnit.METER_PER_SEC,
+ R.string.units_unit_abbreviation_meter_per_sec, R.string.units_unit_name_meter_per_sec);
+ protected static final Unit RPM = new Unit(VehicleUnit.RPM,
+ R.string.units_unit_abbreviation_rpm, R.string.units_unit_name_rpm);
+ protected static final Unit HERTZ = new Unit(VehicleUnit.HERTZ,
+ R.string.units_unit_abbreviation_hertz, R.string.units_unit_name_hertz);
+ protected static final Unit PERCENTILE = new Unit(VehicleUnit.PERCENTILE,
+ R.string.units_unit_abbreviation_percentile, R.string.units_unit_name_percentile);
+ protected static final Unit MILLIMETER = new Unit(VehicleUnit.MILLIMETER,
+ R.string.units_unit_abbreviation_millimeter, R.string.units_unit_name_millimeter);
+ protected static final Unit METER = new Unit(VehicleUnit.METER,
+ R.string.units_unit_abbreviation_meter, R.string.units_unit_name_meter);
+ protected static final Unit KILOMETER = new Unit(VehicleUnit.KILOMETER,
+ R.string.units_unit_abbreviation_kilometer, R.string.units_unit_name_kilometer);
+ protected static final Unit MILE = new Unit(VehicleUnit.MILE,
+ R.string.units_unit_abbreviation_mile, R.string.units_unit_name_mile);
+ protected static final Unit CELSIUS = new Unit(VehicleUnit.CELSIUS,
+ R.string.units_unit_abbreviation_celsius, R.string.units_unit_name_celsius);
+ protected static final Unit FAHRENHEIT = new Unit(VehicleUnit.FAHRENHEIT,
+ R.string.units_unit_abbreviation_fahrenheit, R.string.units_unit_name_fahrenheit);
+ protected static final Unit KELVIN = new Unit(VehicleUnit.KELVIN,
+ R.string.units_unit_abbreviation_kelvin, R.string.units_unit_name_kelvin);
+ protected static final Unit MILLILITER = new Unit(VehicleUnit.MILLILITER,
+ R.string.units_unit_abbreviation_milliliter, R.string.units_unit_name_milliliter);
+ protected static final Unit LITER = new Unit(VehicleUnit.LITER,
+ R.string.units_unit_abbreviation_liter, R.string.units_unit_name_liter);
+ protected static final Unit US_GALLON = new Unit(VehicleUnit.US_GALLON,
+ R.string.units_unit_abbreviation_us_gallon, R.string.units_unit_name_us_gallon);
+ protected static final Unit IMPERIAL_GALLON = new Unit(VehicleUnit.IMPERIAL_GALLON,
+ R.string.units_unit_abbreviation_imperial_gallon,
+ R.string.units_unit_name_imperial_gallon);
+ protected static final Unit NANO_SECS = new Unit(VehicleUnit.NANO_SECS,
+ R.string.units_unit_abbreviation_nano_secs, R.string.units_unit_name_nano_secs);
+ protected static final Unit SECS = new Unit(VehicleUnit.SECS,
+ R.string.units_unit_abbreviation_secs, R.string.units_unit_name_secs);
+ protected static final Unit YEAR = new Unit(VehicleUnit.YEAR,
+ R.string.units_unit_abbreviation_year, R.string.units_unit_name_year);
+ protected static final Unit KILOPASCAL = new Unit(VehicleUnit.KILOPASCAL,
+ R.string.units_unit_abbreviation_kilopascal, R.string.units_unit_name_kilopascal);
+ protected static final Unit WATT_HOUR = new Unit(VehicleUnit.WATT_HOUR,
+ R.string.units_unit_abbreviation_watt_hour, R.string.units_unit_name_watt_hour);
+ protected static final Unit MILLIAMPERE = new Unit(VehicleUnit.MILLIAMPERE,
+ R.string.units_unit_abbreviation_milliampere, R.string.units_unit_name_milliampere);
+ protected static final Unit MILLIVOLT = new Unit(VehicleUnit.MILLIVOLT,
+ R.string.units_unit_abbreviation_millivolt, R.string.units_unit_name_millivolt);
+ protected static final Unit MILLIWATTS = new Unit(VehicleUnit.MILLIWATTS,
+ R.string.units_unit_abbreviation_milliwatts, R.string.units_unit_name_milliwatts);
+ protected static final Unit AMPERE_HOURS = new Unit(VehicleUnit.AMPERE_HOURS,
+ R.string.units_unit_abbreviation_ampere_hour, R.string.units_unit_name_ampere_hour);
+ protected static final Unit KILOWATT_HOUR = new Unit(VehicleUnit.KILOWATT_HOUR,
+ R.string.units_unit_abbreviation_kilowatt_hour, R.string.units_unit_name_kilowatt_hour);
+ protected static final Unit PSI = new Unit(VehicleUnit.PSI,
+ R.string.units_unit_abbreviation_psi, R.string.units_unit_name_psi);
+ protected static final Unit BAR = new Unit(VehicleUnit.BAR,
+ R.string.units_unit_abbreviation_bar, R.string.units_unit_name_bar);
+ protected static final Unit DEGREES = new Unit(VehicleUnit.DEGREES,
+ R.string.units_unit_abbreviation_degrees, R.string.units_unit_name_degrees);
+ protected static final Unit MILES_PER_HOUR = new Unit(VehicleUnit.MILES_PER_HOUR,
+ R.string.units_unit_abbreviation_miles_per_hour,
+ R.string.units_unit_name_miles_per_hour);
+ protected static final Unit KILOMETERS_PER_HOUR = new Unit(VehicleUnit.KILOMETERS_PER_HOUR,
+ R.string.units_unit_abbreviation_kilometers_per_hour,
+ R.string.units_unit_name_kilometers_per_hour);
+
+ public static final HashMap<Integer, Unit> MAP = createMap();
+
+ private static HashMap<Integer, Unit> createMap() {
+ HashMap<Integer, Unit> map = new HashMap();
+ map.put(VehicleUnit.METER_PER_SEC, METER_PER_SEC);
+ map.put(VehicleUnit.RPM, RPM);
+ map.put(VehicleUnit.HERTZ, HERTZ);
+ map.put(VehicleUnit.PERCENTILE, PERCENTILE);
+ map.put(VehicleUnit.MILLIMETER, MILLIMETER);
+ map.put(VehicleUnit.METER, METER);
+ map.put(VehicleUnit.KILOMETER, KILOMETER);
+ map.put(VehicleUnit.MILE, MILE);
+ map.put(VehicleUnit.CELSIUS, CELSIUS);
+ map.put(VehicleUnit.FAHRENHEIT, FAHRENHEIT);
+ map.put(VehicleUnit.KELVIN, KELVIN);
+ map.put(VehicleUnit.MILLILITER, MILLILITER);
+ map.put(VehicleUnit.LITER, LITER);
+ map.put(VehicleUnit.US_GALLON, US_GALLON);
+ map.put(VehicleUnit.IMPERIAL_GALLON, IMPERIAL_GALLON);
+ map.put(VehicleUnit.NANO_SECS, NANO_SECS);
+ map.put(VehicleUnit.SECS, SECS);
+ map.put(VehicleUnit.YEAR, YEAR);
+ map.put(VehicleUnit.KILOPASCAL, KILOPASCAL);
+ map.put(VehicleUnit.WATT_HOUR, WATT_HOUR);
+ map.put(VehicleUnit.MILLIAMPERE, MILLIAMPERE);
+ map.put(VehicleUnit.MILLIVOLT, MILLIVOLT);
+ map.put(VehicleUnit.MILLIWATTS, MILLIWATTS);
+ map.put(VehicleUnit.AMPERE_HOURS, AMPERE_HOURS);
+ map.put(VehicleUnit.KILOWATT_HOUR, KILOWATT_HOUR);
+ map.put(VehicleUnit.PSI, PSI);
+ map.put(VehicleUnit.BAR, BAR);
+ map.put(VehicleUnit.DEGREES, DEGREES);
+ map.put(VehicleUnit.MILES_PER_HOUR, MILES_PER_HOUR);
+ map.put(VehicleUnit.KILOMETERS_PER_HOUR, KILOMETERS_PER_HOUR);
+
+ return map;
+ }
+
+ private UnitsMap() {
+ }
+}
diff --git a/src/com/android/car/settings/units/UnitsSettingsFragment.java b/src/com/android/car/settings/units/UnitsSettingsFragment.java
new file mode 100644
index 0000000..7c0bed9
--- /dev/null
+++ b/src/com/android/car/settings/units/UnitsSettingsFragment.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.units;
+
+import androidx.annotation.LayoutRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Fragment to host Units-related preferences. */
+public class UnitsSettingsFragment extends SettingsFragment {
+
+ @Override
+ @LayoutRes
+ protected int getPreferenceScreenResId() {
+ return R.xml.units_fragment;
+ }
+
+}
diff --git a/src/com/android/car/settings/units/UnitsTemperaturePreferenceController.java b/src/com/android/car/settings/units/UnitsTemperaturePreferenceController.java
new file mode 100644
index 0000000..fa1968a
--- /dev/null
+++ b/src/com/android/car/settings/units/UnitsTemperaturePreferenceController.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.units;
+
+import android.car.VehiclePropertyIds;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.common.FragmentController;
+
+/** Controls {@link Unit} used for HVAC Temperature Display. */
+public class UnitsTemperaturePreferenceController extends UnitsBasePreferenceController {
+
+ public UnitsTemperaturePreferenceController(Context context, String preferenceKey,
+ FragmentController fragmentController,
+ CarUxRestrictions uxRestrictions) {
+ super(context, preferenceKey, fragmentController, uxRestrictions);
+ }
+
+ @Override
+ protected Class<ListPreference> getPreferenceType() {
+ return ListPreference.class;
+ }
+
+ @Override
+ protected int getPropertyId() {
+ return VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS;
+ }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowCarUnitsManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowCarUnitsManager.java
new file mode 100644
index 0000000..91ed48e
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowCarUnitsManager.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import com.android.car.settings.units.CarUnitsManager;
+import com.android.car.settings.units.Unit;
+import com.android.car.settings.units.UnitsMap;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.HashMap;
+
+/**
+ * Shadow class for {@link CarUnitsManager}.
+ */
+@Implements(CarUnitsManager.class)
+public class ShadowCarUnitsManager {
+ private static boolean sConnected = false;
+ private static CarUnitsManager.OnCarServiceListener sListener;
+ private static HashMap<Integer, Unit[]> sSupportedUnits = new HashMap<>();
+ private static HashMap<Integer, Unit> sUnitsBeingUsed = new HashMap<>();
+
+ @Implementation
+ protected void connect() {
+ sConnected = true;
+ }
+
+ @Implementation
+ protected void disconnect() {
+ sConnected = false;
+ }
+
+ @Implementation
+ protected static Unit[] getUnitsSupportedByProperty(int propertyId) {
+ return sSupportedUnits.get(propertyId);
+ }
+
+ @Implementation
+ protected static Unit getUnitUsedByProperty(int propertyId) {
+ return sUnitsBeingUsed.get(propertyId);
+ }
+
+ @Implementation
+ protected static void setUnitUsedByProperty(int propertyId, int unitId) {
+ sUnitsBeingUsed.put(propertyId, UnitsMap.MAP.get(unitId));
+ }
+
+ @Implementation
+ protected static void registerCarServiceListener(
+ CarUnitsManager.OnCarServiceListener listener) {
+ sListener = listener;
+ }
+
+ @Implementation
+ protected static void unregisterCarServiceListener() {
+ sListener = null;
+ }
+
+ @Resetter
+ public static void reset() {
+ sConnected = false;
+ sListener = null;
+ sSupportedUnits = new HashMap<>();
+ sUnitsBeingUsed = new HashMap<>();
+ }
+
+ public static void setUnitsSupportedByProperty(int propertyId, Unit[] units) {
+ sSupportedUnits.put(propertyId, units);
+ }
+
+ public static boolean isConnected() {
+ return sConnected;
+ }
+
+ public static CarUnitsManager.OnCarServiceListener getListener() {
+ return sListener;
+ }
+}
diff --git a/tests/robotests/src/com/android/car/settings/units/UnitsBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/units/UnitsBasePreferenceControllerTest.java
new file mode 100644
index 0000000..becafe8
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/units/UnitsBasePreferenceControllerTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.units;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCar;
+import com.android.car.settings.testutils.ShadowCarUnitsManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCar.class, ShadowCarUnitsManager.class})
+public class UnitsBasePreferenceControllerTest {
+
+ private static final int TEST_PROPERTY_ID = -1;
+ private static final Unit[] AVAILABLE_UNITS =
+ {UnitsMap.YEAR, UnitsMap.SECS, UnitsMap.NANO_SECS};
+ private static final Unit DEFAULT_UNIT = UnitsMap.YEAR;
+
+ private Context mContext;
+ private ListPreference mPreference;
+ private PreferenceControllerTestHelper<TestUnitsBasePreferenceController> mControllerHelper;
+ private TestUnitsBasePreferenceController mController;
+ private CarUnitsManager mCarUnitsManager;
+
+ @Mock
+ private CarPropertyManager mCarPropertyManager;
+ @Mock
+ private CarPropertyValue mCarPropertyValue;
+
+ private static class TestUnitsBasePreferenceController extends UnitsBasePreferenceController {
+
+ private TestUnitsBasePreferenceController(Context context, String preferenceKey,
+ FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+ super(context, preferenceKey, fragmentController, uxRestrictions);
+ }
+
+ @Override
+ protected int getPropertyId() {
+ return TEST_PROPERTY_ID;
+ }
+
+
+ @Override
+ protected Class<ListPreference> getPreferenceType() {
+ return ListPreference.class;
+ }
+ }
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ ShadowCarUnitsManager.setUnitsSupportedByProperty(TEST_PROPERTY_ID, AVAILABLE_UNITS);
+ mCarUnitsManager = new CarUnitsManager(mContext);
+ mCarUnitsManager.setUnitUsedByProperty(TEST_PROPERTY_ID, DEFAULT_UNIT.getId());
+ mPreference = new ListPreference(mContext);
+ mControllerHelper = new PreferenceControllerTestHelper<>(
+ mContext, TestUnitsBasePreferenceController.class, mPreference);
+ mController = mControllerHelper.getController();
+ mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+ }
+
+ @After
+ public void tearDown() {
+ ShadowCarUnitsManager.reset();
+ }
+
+
+ @Test
+ public void onCreate_connectsCarUnitsManager() {
+ assertThat(ShadowCarUnitsManager.isConnected()).isTrue();
+ }
+
+ @Test
+ public void onCreate_registersCarServiceListener() {
+ assertThat(ShadowCarUnitsManager.getListener())
+ .isEqualTo(mController.mOnCarServiceListener);
+ }
+
+ @Test
+ public void onCreate_preferenceIsConditionallyUnavailable() {
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(PreferenceController.CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void onCarServiceConnected_availableUnitsExist_preferenceIsAvailable() {
+ mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(PreferenceController.AVAILABLE);
+ }
+
+ @Test
+ public void onCarServiceConnected_noAvailableUnits_preferenceIsConditionallyUnavailable() {
+ ShadowCarUnitsManager.setUnitsSupportedByProperty(TEST_PROPERTY_ID, null);
+ mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(PreferenceController.CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void onCarServiceConnected_setsEntriesOfSupportedUnits() {
+ mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+ CharSequence[] expectedEntries = mController.getEntriesOfSupportedUnits();
+
+ assertThat(mPreference.getEntries()).isEqualTo(expectedEntries);
+ }
+
+ @Test
+ public void onCarServiceConnected_setsSupportedUnitsIdsAsEntryValues() {
+ mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+ CharSequence[] expectedEntryValues = mController.getIdsOfSupportedUnits();
+
+ assertThat(mPreference.getEntryValues()).isEqualTo(expectedEntryValues);
+ }
+
+ @Test
+ public void onCarServiceConnected_setsUnitBeingUsedAsPreferenceValue() {
+ mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+ String expectedValue = Integer.toString(DEFAULT_UNIT.getId());
+
+ assertThat(mPreference.getValue()).isEqualTo(expectedValue);
+ }
+
+ @Test
+ public void onPreferenceChanged_runsSetUnitUsedByPropertyWithNewUnit() {
+ mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+ mController.handlePreferenceChanged(mPreference, Integer.toString(UnitsMap.SECS.getId()));
+
+ assertThat(mCarUnitsManager.getUnitUsedByProperty(TEST_PROPERTY_ID))
+ .isEqualTo(UnitsMap.SECS);
+ }
+
+ @Test
+ public void onPropertyChanged_propertyStatusIsAvailable_setsNewUnitIdAsValue() {
+ mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+ Unit newUnit = UnitsMap.SECS;
+ when(mCarPropertyValue.getStatus()).thenReturn(CarPropertyValue.STATUS_AVAILABLE);
+ when(mCarPropertyValue.getValue()).thenReturn(newUnit.getId());
+ mController.mCarPropertyEventCallback.onChangeEvent(mCarPropertyValue);
+
+ assertThat(mPreference.getValue()).isEqualTo(Integer.toString(newUnit.getId()));
+ }
+
+ @Test
+ public void onPropertyChanged_propertyStatusIsAvailable_setsNewUnitAbbreviationAsSummary() {
+ mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+ Unit newUnit = UnitsMap.SECS;
+ when(mCarPropertyValue.getStatus()).thenReturn(CarPropertyValue.STATUS_AVAILABLE);
+ when(mCarPropertyValue.getValue()).thenReturn(newUnit.getId());
+ mController.mCarPropertyEventCallback.onChangeEvent(mCarPropertyValue);
+
+ assertThat(mPreference.getSummary())
+ .isEqualTo(mContext.getString(newUnit.getAbbreviationResId()));
+ }
+
+ @Test
+ public void onDestroy_disconnectsCarUnitsManager() {
+ mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+ mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+ assertThat(ShadowCarUnitsManager.isConnected()).isFalse();
+ }
+
+ @Test
+ public void onDestroy_unregistersCarServiceListener() {
+ mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+ mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+ assertThat(ShadowCarUnitsManager.getListener()).isNull();
+ }
+}