aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorBill Yi <byi@google.com>2018-11-28 18:35:00 -0800
committerBill Yi <byi@google.com>2018-11-28 18:35:00 -0800
commit0f8320f6b95736ea4b703764a7d11a8a6ca13674 (patch)
treed7b300e81f05e45345ada1ffcaf39b2512dd8f6f /tests
parentf02b56678700a4035d0ad8882f7d20371bb96ee2 (diff)
parent911e6566751a60c29eada6ad0679694bed11be4f (diff)
downloadCar-pie-platform-release.tar.gz
Merge pi-qpr1-release PQ1A.181105.017.A1 to pi-platform-releasepie-platform-releasepie-cuttlefish-testing
Change-Id: Ibafbc25e1d704d7e84a168b32d35a165dd41e06f
Diffstat (limited to 'tests')
-rw-r--r--tests/CarTrustAgentClientApp/Android.mk15
-rw-r--r--tests/CarTrustAgentClientApp/AndroidManifest.xml34
-rw-r--r--tests/CarTrustAgentClientApp/README.txt2
-rw-r--r--tests/CarTrustAgentClientApp/res/layout/phone_enrolment_activity.xml54
-rw-r--r--tests/CarTrustAgentClientApp/res/values/strings.xml22
-rw-r--r--tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentActivity.java61
-rw-r--r--tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentController.java183
-rw-r--r--tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneUnlockController.java149
-rw-r--r--tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java355
-rw-r--r--tests/CarTrustAgentClientApp/src/com/android/car/trust/client/Utils.java46
-rw-r--r--tests/EmbeddedKitchenSinkApp/AndroidManifest.xml2
-rw-r--r--tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml55
-rw-r--r--tests/EmbeddedKitchenSinkApp/res/layout/list_item.xml22
-rw-r--r--tests/EmbeddedKitchenSinkApp/res/values/strings.xml5
-rw-r--r--tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java70
-rw-r--r--tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/connectivity/ConnectivityFragment.java154
-rw-r--r--tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java141
-rw-r--r--tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java23
-rw-r--r--tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java32
-rw-r--r--tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java21
-rw-r--r--tests/carservice_test/AndroidManifest.xml5
-rw-r--r--tests/carservice_test/src/com/android/car/MockedCarTestBase.java4
-rw-r--r--tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java (renamed from tests/carservice_test/src/com/android/car/GarageModeTest.java)143
-rw-r--r--tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java52
-rw-r--r--tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java655
-rw-r--r--tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java136
-rw-r--r--tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java2
27 files changed, 2107 insertions, 336 deletions
diff --git a/tests/CarTrustAgentClientApp/Android.mk b/tests/CarTrustAgentClientApp/Android.mk
new file mode 100644
index 0000000000..7945ee5fa8
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CarTrustAgentClient
+
+LOCAL_USE_AAPT2 := true
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_ANDROID_LIBRARIES := androidx.legacy_legacy-support-v4
+
+LOCAL_CERTIFICATE := platform
+LOCAL_MODULE_TAGS := optional
+LOCAL_MIN_SDK_VERSION := 23
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/tests/CarTrustAgentClientApp/AndroidManifest.xml b/tests/CarTrustAgentClientApp/AndroidManifest.xml
new file mode 100644
index 0000000000..35a9a6dbd0
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.car.trust.client">
+
+ <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23"/>
+
+ <!-- Need Bluetooth LE -->
+ <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
+
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+
+ <!-- Needed to unlock user -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
+ <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
+ <application android:label="@string/app_name">
+ <activity
+ android:name=".PhoneEnrolmentActivity"
+ android:label="@string/app_name"
+ android:exported="true"
+ android:launchMode="singleInstance">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/CarTrustAgentClientApp/README.txt b/tests/CarTrustAgentClientApp/README.txt
new file mode 100644
index 0000000000..bf6c4444b9
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/README.txt
@@ -0,0 +1,2 @@
+IMPORTANT NOTE: This is a reference app to smart unlock paired HU during development.
+Consider moving the functionality to a more proper place.
diff --git a/tests/CarTrustAgentClientApp/res/layout/phone_enrolment_activity.xml b/tests/CarTrustAgentClientApp/res/layout/phone_enrolment_activity.xml
new file mode 100644
index 0000000000..620e04e81a
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/res/layout/phone_enrolment_activity.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:weightSum="1">
+ <ScrollView
+ android:id="@+id/scroll"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:scrollbars="vertical"
+ android:layout_weight="0.80">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/output"/>
+ </ScrollView>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="0.10"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/enroll_scan"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ android:text="@string/enroll_scan"/>
+ <Button
+ android:id="@+id/enroll_button"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3"
+ android:text="@string/enroll_button"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="0.10"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/unlock_scan"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ android:text="@string/unlock_scan"/>
+ <Button
+ android:id="@+id/unlock_button"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3"
+ android:text="@string/unlock_button"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/tests/CarTrustAgentClientApp/res/values/strings.xml b/tests/CarTrustAgentClientApp/res/values/strings.xml
new file mode 100644
index 0000000000..5c9b4db705
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">CarTrustAgentClient</string>
+
+ <!-- service/characteristics uuid for unlocking a device -->
+ <string name="unlock_service_uuid">5e2a68a1-27be-43f9-8d1e-4546976fabd7</string>
+ <string name="unlock_escrow_token_uiid">5e2a68a2-27be-43f9-8d1e-4546976fabd7</string>
+ <string name="unlock_handle_uiid">5e2a68a3-27be-43f9-8d1e-4546976fabd7</string>
+
+ <!-- service/characteristics uuid for adding new escrow token -->
+ <string name="enrollment_service_uuid">5e2a68a4-27be-43f9-8d1e-4546976fabd7</string>
+ <string name="enrollment_handle_uuid">5e2a68a5-27be-43f9-8d1e-4546976fabd7</string>
+ <string name="enrollment_token_uuid">5e2a68a6-27be-43f9-8d1e-4546976fabd7</string>
+
+ <string name="pref_key_token_handle">token-handle-key</string>
+ <string name="pref_key_escrow_token">escrow-token-key</string>
+
+ <string name="enroll_button">Enroll new token</string>
+ <string name="enroll_scan">Scan to enroll</string>
+ <string name="unlock_button">Unlock</string>
+ <string name="unlock_scan">Scan to unlock</string>
+</resources>
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentActivity.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentActivity.java
new file mode 100644
index 0000000000..c1d30c1872
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 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.trust.client;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+/**
+ * Activity to allow the user to add an escrow token to a remote device. <p/>
+ *
+ * For this to work properly, the correct permissions must be set in the system config. In AOSP,
+ * this config is in frameworks/base/core/res/res/values/config.xml <p/>
+ *
+ * The config must set config_allowEscrowTokenForTrustAgent to true. For the desired car
+ * experience, the config should also set config_strongAuthRequiredOnBoot to false.
+ */
+public class PhoneEnrolmentActivity extends Activity {
+
+ private static final int FINE_LOCATION_REQUEST_CODE = 42;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.phone_enrolment_activity);
+
+ PhoneEnrolmentController enrolmentController = new PhoneEnrolmentController(this);
+ enrolmentController.bind(findViewById(R.id.output), findViewById(R.id.enroll_scan),
+ findViewById(R.id.enroll_button));
+
+ PhoneUnlockController unlockController = new PhoneUnlockController(this);
+ unlockController.bind(findViewById(R.id.output), findViewById(R.id.unlock_scan),
+ findViewById(R.id.unlock_button));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(
+ new String[] { android.Manifest.permission.ACCESS_FINE_LOCATION },
+ FINE_LOCATION_REQUEST_CODE);
+ }
+ }
+}
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentController.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentController.java
new file mode 100644
index 0000000000..030e3d2a4b
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentController.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2018 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.trust.client;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.preference.PreferenceManager;
+import android.util.Base64;
+import android.util.Log;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.nio.ByteBuffer;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * A controller that sets up a {@link SimpleBleClient} to connect to the BLE enrollment service.
+ * It also binds the UI components to control the enrollment process.
+ */
+public class PhoneEnrolmentController {
+
+ private final SimpleBleClient.ClientCallback mCallback = new SimpleBleClient.ClientCallback() {
+ @Override
+ public void onDeviceConnected(BluetoothDevice device) {
+ appendOutputText("Device connected: " + device.getName()
+ + " addr: " + device.getAddress());
+ }
+
+ @Override
+ public void onDeviceDisconnected() {
+ appendOutputText("Device disconnected");
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+
+ Log.d(Utils.LOG_TAG, "onCharacteristicChanged: "
+ + Utils.getLong(characteristic.getValue()));
+ if (characteristic.getUuid().equals(mEnrolmentTokenHandle.getUuid())) {
+ // Store the new token handle that the BLE server is sending us. This required
+ // to unlock the device.
+ long handle = Utils.getLong(characteristic.getValue());
+ storeHandle(handle);
+ appendOutputText("Token handle received: " + handle);
+ }
+ }
+
+ @Override
+ public void onServiceDiscovered(BluetoothGattService service) {
+ if (!service.getUuid().equals(mEnrolmentServiceUuid.getUuid())) {
+ Log.d(Utils.LOG_TAG, "Service UUID: " + service.getUuid()
+ + " does not match Enrolment UUID " + mEnrolmentServiceUuid.getUuid());
+ return;
+ }
+
+ Log.d(Utils.LOG_TAG, "Enrolment Service # characteristics: "
+ + service.getCharacteristics().size());
+ mEnrolmentEscrowToken = Utils.getCharacteristic(
+ R.string.enrollment_token_uuid, service, mContext);
+ mEnrolmentTokenHandle = Utils.getCharacteristic(
+ R.string.enrollment_handle_uuid, service, mContext);
+ mClient.setCharacteristicNotification(mEnrolmentTokenHandle, true /* enable */);
+ appendOutputText("Enrolment BLE client successfully connected");
+
+ mHandler.post(() -> {
+ // Services are now set up, allow users to enrol new escrow tokens.
+ mEnrolButton.setEnabled(true);
+ mEnrolButton.setAlpha(1.0f);
+ });
+ }
+ };
+
+ private String mTokenHandleKey;
+ private String mEscrowTokenKey;
+
+ // BLE characteristics associated with the enrollment/add escrow token service.
+ private BluetoothGattCharacteristic mEnrolmentTokenHandle;
+ private BluetoothGattCharacteristic mEnrolmentEscrowToken;
+
+ private ParcelUuid mEnrolmentServiceUuid;
+
+ private SimpleBleClient mClient;
+ private Context mContext;
+
+ private TextView mTextView;
+ private Handler mHandler;
+
+ private Button mEnrolButton;
+
+ public PhoneEnrolmentController(Context context) {
+ mContext = context;
+
+ mTokenHandleKey = context.getString(R.string.pref_key_token_handle);
+ mEscrowTokenKey = context.getString(R.string.pref_key_escrow_token);
+
+ mClient = new SimpleBleClient(context);
+ mEnrolmentServiceUuid = new ParcelUuid(
+ UUID.fromString(mContext.getString(R.string.enrollment_service_uuid)));
+ mClient.addCallback(mCallback /* callback */);
+
+ mHandler = new Handler(mContext.getMainLooper());
+ }
+
+ /**
+ * Binds the views to the actions that can be performed by this controller.
+ *
+ * @param textView A text view used to display results from various BLE actions
+ * @param scanButton Button used to start scanning for available BLE devices.
+ * @param enrolButton Button used to send new escrow token to remote device.
+ */
+ public void bind(TextView textView, Button scanButton, Button enrolButton) {
+ mTextView = textView;
+ mEnrolButton = enrolButton;
+
+ scanButton.setOnClickListener((view) -> mClient.start(mEnrolmentServiceUuid));
+
+ mEnrolButton.setEnabled(false);
+ mEnrolButton.setAlpha(0.3f);
+ mEnrolButton.setOnClickListener((view) -> {
+ appendOutputText("Sending new escrow token to remote device");
+
+ byte[] token = generateEscrowToken();
+ sendEnrolmentRequest(token);
+
+ // WARNING: Store the token so it can be used later for unlocking. This token
+ // should NEVER be stored on the device that is being unlocked. It should
+ // always be securely stored on a remote device that will trigger the unlock.
+ storeToken(token);
+ });
+ }
+
+ /**
+ * @return A random byte array that is used as the escrow token for remote device unlock.
+ */
+ private byte[] generateEscrowToken() {
+ Random random = new Random();
+ ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
+ buffer.putLong(0, random.nextLong());
+ return buffer.array();
+ }
+
+ private void sendEnrolmentRequest(byte[] token) {
+ mEnrolmentEscrowToken.setValue(token);
+ mClient.writeCharacteristic(mEnrolmentEscrowToken);
+ storeToken(token);
+ }
+
+ private void storeHandle(long handle) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ prefs.edit().putLong(mTokenHandleKey, handle).apply();
+ }
+
+ private void storeToken(byte[] token) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ String byteArray = Base64.encodeToString(token, Base64.DEFAULT);
+ prefs.edit().putString(mEscrowTokenKey, byteArray).apply();
+ }
+
+ private void appendOutputText(final String text) {
+ mHandler.post(() -> mTextView.append("\n" + text));
+ }
+}
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneUnlockController.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneUnlockController.java
new file mode 100644
index 0000000000..78e50b41a6
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneUnlockController.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2018 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.trust.client;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.preference.PreferenceManager;
+import android.util.Base64;
+import android.util.Log;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.util.UUID;
+
+/**
+ * A controller that sets up a {@link SimpleBleClient} to connect to the BLE unlock service.
+ */
+public class PhoneUnlockController {
+
+ private final SimpleBleClient.ClientCallback mCallback = new SimpleBleClient.ClientCallback() {
+ @Override
+ public void onDeviceConnected(BluetoothDevice device) {
+ appendOutputText("Device connected: " + device.getName()
+ + " addr: " + device.getAddress());
+ }
+
+ @Override
+ public void onDeviceDisconnected() {
+ appendOutputText("Device disconnected");
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ // Not expecting any characteristics changes for the unlocking client.
+ }
+
+ @Override
+ public void onServiceDiscovered(BluetoothGattService service) {
+ if (!service.getUuid().equals(mUnlockServiceUuid.getUuid())) {
+ Log.d(Utils.LOG_TAG, "Service UUID: " + service.getUuid()
+ + " does not match Enrolment UUID " + mUnlockServiceUuid.getUuid());
+ return;
+ }
+
+ Log.d(Utils.LOG_TAG, "Unlock Service # characteristics: "
+ + service.getCharacteristics().size());
+ mUnlockEscrowToken = Utils.getCharacteristic(
+ R.string.unlock_escrow_token_uiid, service, mContext);
+ mUnlockTokenHandle = Utils.getCharacteristic(
+ R.string.unlock_handle_uiid, service, mContext);
+ appendOutputText("Unlock BLE client successfully connected");
+
+ mHandler.post(() -> {
+ // Services are now set up, allow users to enrol new escrow tokens.
+ mUnlockButton.setEnabled(true);
+ mUnlockButton.setAlpha(1.0f);
+ });
+ }
+ };
+
+ private String mTokenHandleKey;
+ private String mEscrowTokenKey;
+
+ // BLE characteristics associated with the enrolment/add escrow token service.
+ private BluetoothGattCharacteristic mUnlockTokenHandle;
+ private BluetoothGattCharacteristic mUnlockEscrowToken;
+
+ private ParcelUuid mUnlockServiceUuid;
+
+ private SimpleBleClient mClient;
+ private Context mContext;
+
+ private TextView mTextView;
+ private Handler mHandler;
+
+ private Button mUnlockButton;
+
+ public PhoneUnlockController(Context context) {
+ mContext = context;
+
+ mTokenHandleKey = context.getString(R.string.pref_key_token_handle);
+ mEscrowTokenKey = context.getString(R.string.pref_key_escrow_token);
+
+ mClient = new SimpleBleClient(context);
+ mUnlockServiceUuid = new ParcelUuid(
+ UUID.fromString(mContext.getString(R.string.unlock_service_uuid)));
+ mClient.addCallback(mCallback /* callback */);
+
+ mHandler = new Handler(mContext.getMainLooper());
+ }
+
+ /**
+ * Binds the views to the actions that can be performed by this controller.
+ *
+ * @param textView A text view used to display results from various BLE actions
+ * @param scanButton Button used to start scanning for available BLE devices.
+ * @param enrolButton Button used to send new escrow token to remote device.
+ */
+ public void bind(TextView textView, Button scanButton, Button enrolButton) {
+ mTextView = textView;
+ mUnlockButton = enrolButton;
+
+ scanButton.setOnClickListener((view) -> mClient.start(mUnlockServiceUuid));
+
+ mUnlockButton.setEnabled(false);
+ mUnlockButton.setAlpha(0.3f);
+ mUnlockButton.setOnClickListener((view) -> {
+ appendOutputText("Sending unlock token and handle to remote device");
+ sendUnlockRequest();
+ });
+ }
+
+ private void sendUnlockRequest() {
+ // Retrieve stored token and handle and write to remote device.
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ long handle = prefs.getLong(mTokenHandleKey, -1);
+ byte[] token = Base64.decode(prefs.getString(mEscrowTokenKey, null), Base64.DEFAULT);
+
+ mUnlockEscrowToken.setValue(token);
+ mUnlockTokenHandle.setValue(Utils.getBytes(handle));
+
+ mClient.writeCharacteristic(mUnlockEscrowToken);
+ mClient.writeCharacteristic(mUnlockTokenHandle);
+ }
+
+ private void appendOutputText(final String text) {
+ mHandler.post(() -> mTextView.append("\n" + text));
+ }
+}
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java
new file mode 100644
index 0000000000..c0fecb31d4
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2018 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.trust.client;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.Context;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * A simple client that supports the scanning and connecting to available BLE devices. Should be
+ * used along with {@link SimpleBleServer}.
+ */
+public class SimpleBleClient {
+ public interface ClientCallback {
+ /**
+ * Called when a device that has a matching service UUID is found.
+ **/
+ void onDeviceConnected(BluetoothDevice device);
+
+ void onDeviceDisconnected();
+
+ void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic);
+
+ /**
+ * Called for each {@link BluetoothGattService} that is discovered on the
+ * {@link BluetoothDevice} after a matching scan result and connection.
+ *
+ * @param service {@link BluetoothGattService} that has been discovered.
+ */
+ void onServiceDiscovered(BluetoothGattService service);
+ }
+
+ /**
+ * Wrapper class to allow queuing of BLE actions. The BLE stack allows only one action to be
+ * executed at a time.
+ */
+ public static class BleAction {
+ public static final int ACTION_WRITE = 0;
+ public static final int ACTION_READ = 1;
+
+ private int mAction;
+ private BluetoothGattCharacteristic mCharacteristic;
+
+ public BleAction(BluetoothGattCharacteristic characteristic, int action) {
+ mAction = action;
+ mCharacteristic = characteristic;
+ }
+
+ public int getAction() {
+ return mAction;
+ }
+
+ public BluetoothGattCharacteristic getCharacteristic() {
+ return mCharacteristic;
+ }
+ }
+
+ private static final long SCAN_TIME_MS = 10000;
+
+ private final Queue<BleAction> mBleActionQueue = new ConcurrentLinkedQueue<BleAction>();
+ private final List<ClientCallback> mCallbacks = new ArrayList<>();
+ private final Context mContext;
+ private final BluetoothLeScanner mScanner;
+
+ private BluetoothGatt mBtGatt;
+ private ParcelUuid mServiceUuid;
+
+ public SimpleBleClient(@NonNull Context context) {
+ mContext = context;
+ BluetoothManager btManager = (BluetoothManager) mContext.getSystemService(
+ Context.BLUETOOTH_SERVICE);
+ mScanner = btManager.getAdapter().getBluetoothLeScanner();
+ }
+
+ /**
+ * Start scanning for a BLE devices with the specified service uuid.
+ *
+ * @param parcelUuid {@link ParcelUuid} used to identify the device that should be used for
+ * this client. This uuid should be the same as the one that is set in the
+ * {@link android.bluetooth.le.AdvertiseData.Builder} by the advertising
+ * device.
+ */
+ public void start(ParcelUuid parcelUuid) {
+ mServiceUuid = parcelUuid;
+
+ // We only want to scan for devices that have the correct uuid set in its advertise data.
+ List<ScanFilter> filters = new ArrayList<ScanFilter>();
+ ScanFilter.Builder serviceFilter = new ScanFilter.Builder();
+ serviceFilter.setServiceUuid(mServiceUuid);
+ filters.add(serviceFilter.build());
+
+ ScanSettings.Builder settings = new ScanSettings.Builder();
+ settings.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
+
+ Log.d(Utils.LOG_TAG, "Start scanning for uuid: " + mServiceUuid.getUuid());
+ mScanner.startScan(filters, settings.build(), mScanCallback);
+
+ Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mScanner.stopScan(mScanCallback);
+ Log.d(Utils.LOG_TAG, "Stopping Scanner");
+ }
+ }, SCAN_TIME_MS);
+ }
+
+ private boolean hasServiceUuid(ScanResult result) {
+ if (result.getScanRecord() == null
+ || result.getScanRecord().getServiceUuids() == null
+ || result.getScanRecord().getServiceUuids().size() == 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Writes to a {@link BluetoothGattCharacteristic} if possible, or queues the action until
+ * other actions are complete.
+ *
+ * @param characteristic {@link BluetoothGattCharacteristic} to be written
+ */
+ public void writeCharacteristic(BluetoothGattCharacteristic characteristic) {
+ processAction(new BleAction(characteristic, BleAction.ACTION_WRITE));
+ }
+
+ /**
+ * Reads a {@link BluetoothGattCharacteristic} if possible, or queues the read action until
+ * other actions are complete.
+ *
+ * @param characteristic {@link BluetoothGattCharacteristic} to be read.
+ */
+ public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
+ processAction(new BleAction(characteristic, BleAction.ACTION_READ));
+ }
+
+ /**
+ * Enable or disable notification for specified {@link BluetoothGattCharacteristic}.
+ *
+ * @param characteristic The {@link BluetoothGattCharacteristic} for which to enable
+ * notifications.
+ * @param enabled True if notifications should be enabled, false otherwise.
+ */
+ public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
+ boolean enabled) {
+ mBtGatt.setCharacteristicNotification(characteristic, enabled);
+ }
+
+ /**
+ * Add a {@link ClientCallback} to listen for updates from BLE components
+ */
+ public void addCallback(ClientCallback callback) {
+ mCallbacks.add(callback);
+ }
+
+ public void removeCallback(ClientCallback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ private void processAction(BleAction action) {
+ // Only execute actions if the queue is empty.
+ if (mBleActionQueue.size() > 0) {
+ mBleActionQueue.add(action);
+ return;
+ }
+
+ mBleActionQueue.add(action);
+ executeAction(mBleActionQueue.peek());
+ }
+
+ private void processNextAction() {
+ mBleActionQueue.poll();
+ executeAction(mBleActionQueue.peek());
+ }
+
+ private void executeAction(BleAction action) {
+ if (action == null) {
+ return;
+ }
+
+ Log.d(Utils.LOG_TAG, "Executing BLE Action type: " + action.getAction());
+ int actionType = action.getAction();
+ switch (actionType) {
+ case BleAction.ACTION_WRITE:
+ mBtGatt.writeCharacteristic(action.getCharacteristic());
+ break;
+ case BleAction.ACTION_READ:
+ mBtGatt.readCharacteristic(action.getCharacteristic());
+ break;
+ default:
+ }
+ }
+
+ private String getStatus(int status) {
+ switch (status) {
+ case BluetoothGatt.GATT_FAILURE:
+ return "Failure";
+ case BluetoothGatt.GATT_SUCCESS:
+ return "GATT_SUCCESS";
+ case BluetoothGatt.GATT_READ_NOT_PERMITTED:
+ return "GATT_READ_NOT_PERMITTED";
+ case BluetoothGatt.GATT_WRITE_NOT_PERMITTED:
+ return "GATT_WRITE_NOT_PERMITTED";
+ case BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION:
+ return "GATT_INSUFFICIENT_AUTHENTICATION";
+ case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED:
+ return "GATT_REQUEST_NOT_SUPPORTED";
+ case BluetoothGatt.GATT_INVALID_OFFSET:
+ return "GATT_INVALID_OFFSET";
+ case BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH:
+ return "GATT_INVALID_ATTRIBUTE_LENGTH";
+ case BluetoothGatt.GATT_CONNECTION_CONGESTED:
+ return "GATT_CONNECTION_CONGESTED";
+ default:
+ return "unknown";
+ }
+ }
+
+ private ScanCallback mScanCallback = new ScanCallback() {
+ @Override
+ public void onScanResult(int callbackType, ScanResult result) {
+ BluetoothDevice device = result.getDevice();
+ Log.d(Utils.LOG_TAG, "Scan result found: " + result.getScanRecord().getServiceUuids());
+
+ if (!hasServiceUuid(result)) {
+ return;
+ }
+
+ for (ParcelUuid uuid : result.getScanRecord().getServiceUuids()) {
+ Log.d(Utils.LOG_TAG, "Scan result UUID: " + uuid);
+ if (uuid.equals(mServiceUuid)) {
+ // This client only supports connecting to one service.
+ // Once we find one, stop scanning and open a GATT connection to the device.
+ mScanner.stopScan(mScanCallback);
+ mBtGatt = device.connectGatt(mContext, false /* autoConnect */, mGattCallback);
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void onBatchScanResults(List<ScanResult> results) {
+ for (ScanResult r : results) {
+ Log.d(Utils.LOG_TAG, "Batch scanResult: " + r.getDevice().getName()
+ + " " + r.getDevice().getAddress());
+ }
+ }
+
+ @Override
+ public void onScanFailed(int errorCode) {
+ Log.e(Utils.LOG_TAG, "Scan failed: " + errorCode);
+ }
+ };
+
+ private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ super.onConnectionStateChange(gatt, status, newState);
+
+ String state = "";
+
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ state = "Connected";
+ mBtGatt.discoverServices();
+ for (ClientCallback callback : mCallbacks) {
+ callback.onDeviceConnected(gatt.getDevice());
+ }
+
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ state = "Disconnected";
+ for (ClientCallback callback : mCallbacks) {
+ callback.onDeviceDisconnected();
+ }
+ }
+ Log.d(Utils.LOG_TAG, "Gatt connection status: " + getStatus(status)
+ + " newState: " + state);
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ super.onServicesDiscovered(gatt, status);
+ Log.d(Utils.LOG_TAG, "onServicesDiscovered: " + status);
+
+ List<BluetoothGattService> services = gatt.getServices();
+ if (services == null || services.size() <= 0) {
+ return;
+ }
+
+ // Notify clients of newly discovered services.
+ for (BluetoothGattService service : mBtGatt.getServices()) {
+ Log.d(Utils.LOG_TAG, "Found service: " + service.getUuid() + " notifying clients");
+ for (ClientCallback callback : mCallbacks) {
+ callback.onServiceDiscovered(service);
+ }
+ }
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic, int status) {
+ Log.d(Utils.LOG_TAG, "onCharacteristicWrite: " + status);
+ processNextAction();
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic, int status) {
+ Log.d(Utils.LOG_TAG, "onCharacteristicRead:" + new String(characteristic.getValue()));
+ processNextAction();
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ for (ClientCallback callback : mCallbacks) {
+ callback.onCharacteristicChanged(gatt, characteristic);
+ }
+ processNextAction();
+ }
+ };
+}
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/Utils.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/Utils.java
new file mode 100644
index 0000000000..003a86cc44
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/Utils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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.trust.client;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Context;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+public class Utils {
+
+ public static final String LOG_TAG = "CarTrustAgentClient";
+
+ public static byte[] getBytes(long l) {
+ ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
+ buffer.putLong(0, l);
+ return buffer.array();
+ }
+
+ public static long getLong(byte[] bytes) {
+ ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
+ buffer.put(bytes);
+ buffer.flip();
+ return buffer.getLong();
+ }
+
+ public static BluetoothGattCharacteristic getCharacteristic(int uuidRes,
+ BluetoothGattService service, Context context) {
+ return service.getCharacteristic(UUID.fromString(context.getString(uuidRes)));
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index bc89ad4311..6e8e80f73a 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -42,6 +42,8 @@
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
<uses-permission android:name="android.permission.INJECT_EVENTS" />
<application android:label="@string/app_title"
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml
new file mode 100644
index 0000000000..15422b1116
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <ListView
+ android:id="@+id/networks"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ </ListView>
+ </LinearLayout>
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp">
+ <Button android:id="@+id/networksRefresh"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Refresh"/>
+ <Button android:id="@+id/networkRequestOemPaid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Request OEM-paid"/>
+ <Button android:id="@+id/networkRequestEth1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Request eth1"/>
+ <Button android:id="@+id/networkReleaseNetwork"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Release Request"/>
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/list_item.xml b/tests/EmbeddedKitchenSinkApp/res/layout/list_item.xml
new file mode 100644
index 0000000000..f517913acb
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/list_item.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:paddingTop="2dip"
+ android:paddingBottom="3dip"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textSize="24sp" /> \ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 74c7c2637b..452fec8d81 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -211,14 +211,14 @@
<!-- sensors test -->
<string name="sensor_na">N/A</string>
- <string name="sensor_environment">Environment[%1$s]: temperature=%2$s, pressure=%3$s</string>
+ <string name="sensor_environment">Environment[%1$s]: temperature=%2$s</string>
<string name="sensor_night">Night[%1$s]: isNight=%2$s</string>
<string name="sensor_gear">Gear[%1$s]: gear=%2$s</string>
<string name="sensor_parking_brake">Parking brake[%1$s]: isEngaged=%2$s</string>
<string name="sensor_odometer">Odometer[%1$s]: kms=%2$s</string>
<string name="sensor_rpm">RPM[%1$s]: rpm=%2$s</string>
<string name="sensor_speed">Speed[%1$s]: speed=%2$s</string>
- <string name="sensor_driving_status">Driving status[%1$s]: status=%2$s [bin=%3$s]</string>
+ <string name="sensor_ignition_status">Ignition status[%1$s]: status=%2$s</string>
<string name="sensor_compass">Compass[%1$s]: bear=%2$s, pitch=%3$s, roll=%4$s</string>
<string name="sensor_accelerometer">Accelerometer[%1$s]: x=%2$s, y=%3$s, z=%4$s</string>
<string name="sensor_gyroscope">Gyroscope[%1$s]: x=%2$s, y=%3$s, z=%4$s</string>
@@ -231,6 +231,7 @@
<string name="sensor_traction_control_is_active">Traction Control[%1$s]: isActive=%2$s</string>
<string name="sensor_fuel_level">Fuel Level[%1$s]: %2$s</string>
<string name="sensor_fuel_door_open">Fuel Door Open[%1$s]: %2$s</string>
+ <string name="sensor_engine_oil_level">Engine Oil Level[%1$s]: %2$s</string>
<string name="sensor_engine_is_on">Engine Is On[%1$s]: %2$s</string>
<string name="sensor_ev_battery_level">EV Battery Level[%1$s]: %2$s</string>
<string name="sensor_ev_charge_port_is_open">EV Charge Port Is Open[%1$s]: %2$s</string>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index 7837f2e775..caca03af01 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -23,7 +23,9 @@ import android.car.hardware.power.CarPowerManager;
import android.car.hardware.property.CarPropertyManager;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
import android.support.car.Car;
import android.support.car.CarAppFocusManager;
import android.support.car.CarConnectionCallback;
@@ -42,6 +44,7 @@ import com.google.android.car.kitchensink.audio.AudioTestFragment;
import com.google.android.car.kitchensink.bluetooth.BluetoothHeadsetFragment;
import com.google.android.car.kitchensink.bluetooth.MapMceTestFragment;
import com.google.android.car.kitchensink.cluster.InstrumentClusterFragment;
+import com.google.android.car.kitchensink.connectivity.ConnectivityFragment;
import com.google.android.car.kitchensink.cube.CubesTestFragment;
import com.google.android.car.kitchensink.diagnostic.DiagnosticTestFragment;
import com.google.android.car.kitchensink.displayinfo.DisplayInfoFragment;
@@ -167,6 +170,7 @@ public class KitchenSinkActivity extends CarDrawerActivity {
startActivity(intent);
});
add("activity view", ActivityViewTestFragment.class);
+ add("connectivity", ConnectivityFragment.class);
add("quit", KitchenSinkActivity.this::finish);
}
@@ -183,6 +187,7 @@ public class KitchenSinkActivity extends CarDrawerActivity {
private CarPropertyManager mPropertyManager;
private CarSensorManager mSensorManager;
private CarAppFocusManager mCarAppFocusManager;
+ private Object mPropertyManagerReady = new Object();
public CarHvacManager getHvacManager() {
return mHvacManager;
@@ -212,12 +217,20 @@ public class KitchenSinkActivity extends CarDrawerActivity {
setMainContent(R.layout.kitchen_content);
// Connection to Car Service does not work for non-automotive yet.
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
- mCarApi = Car.createCar(this, mCarConnectionCallback);
- mCarApi.connect();
+ initCarApi();
}
Log.i(TAG, "onCreate");
}
+ private void initCarApi() {
+ if (mCarApi != null && mCarApi.isConnected()) {
+ mCarApi.disconnect();
+ mCarApi = null;
+ }
+ mCarApi = Car.createCar(this, mCarConnectionCallback);
+ mCarApi.connect();
+ }
+
@Override
protected void onStart() {
super.onStart();
@@ -268,18 +281,22 @@ public class KitchenSinkActivity extends CarDrawerActivity {
@Override
public void onConnected(Car car) {
Log.d(TAG, "Connected to Car Service");
- try {
- mHvacManager = (CarHvacManager) mCarApi.getCarManager(android.car.Car.HVAC_SERVICE);
- mPowerManager = (CarPowerManager) mCarApi.getCarManager(
- android.car.Car.POWER_SERVICE);
- mPropertyManager = (CarPropertyManager) mCarApi.getCarManager(
- android.car.Car.PROPERTY_SERVICE);
- mSensorManager = (CarSensorManager) mCarApi.getCarManager(
- android.car.Car.SENSOR_SERVICE);
- mCarAppFocusManager =
- (CarAppFocusManager) mCarApi.getCarManager(Car.APP_FOCUS_SERVICE);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected!", e);
+ synchronized (mPropertyManagerReady) {
+ try {
+ mHvacManager = (CarHvacManager) mCarApi.getCarManager(
+ android.car.Car.HVAC_SERVICE);
+ mPowerManager = (CarPowerManager) mCarApi.getCarManager(
+ android.car.Car.POWER_SERVICE);
+ mPropertyManager = (CarPropertyManager) mCarApi.getCarManager(
+ android.car.Car.PROPERTY_SERVICE);
+ mSensorManager = (CarSensorManager) mCarApi.getCarManager(
+ android.car.Car.SENSOR_SERVICE);
+ mCarAppFocusManager =
+ (CarAppFocusManager) mCarApi.getCarManager(Car.APP_FOCUS_SERVICE);
+ mPropertyManagerReady.notifyAll();
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected!", e);
+ }
}
}
@@ -322,4 +339,29 @@ public class KitchenSinkActivity extends CarDrawerActivity {
getDrawerController().closeDrawer();
}
}
+
+ // Use AsyncTask to refresh Car*Manager after car service connected
+ public void requestRefreshManager(final Runnable r, final Handler h) {
+ final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ synchronized (mPropertyManagerReady) {
+ while (!mCarApi.isConnected()) {
+ try {
+ mPropertyManagerReady.wait();
+ } catch (InterruptedException e) {
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void unused) {
+ h.post(r);
+ }
+ };
+ task.execute();
+ }
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/connectivity/ConnectivityFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/connectivity/ConnectivityFragment.java
new file mode 100644
index 0000000000..0ffa6bf37e
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/connectivity/ConnectivityFragment.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2018 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.google.android.car.kitchensink.connectivity;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.ArrayList;
+
+@SuppressLint("SetTextI18n")
+public class ConnectivityFragment extends Fragment {
+ private static final String TAG = ConnectivityFragment.class.getSimpleName();
+
+ private final Handler mHandler = new Handler();
+ private final ArrayList<String> mNetworks = new ArrayList<>();
+
+ private ConnectivityManager mConnectivityManager;
+ private ArrayAdapter<String> mNetworksAdapter;
+
+ private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ showToast("onAvailable, netId: " + network);
+ refreshNetworks();
+ }
+
+ @Override
+ public void onLost(Network network) {
+ showToast("onLost, netId: " + network);
+ refreshNetworks();
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mConnectivityManager = getActivity().getSystemService(ConnectivityManager.class);
+
+ mConnectivityManager.addDefaultNetworkActiveListener(() -> refreshNetworks());
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.connectivity_fragment, container, false);
+
+ ListView networksView = view.findViewById(R.id.networks);
+ mNetworksAdapter = new ArrayAdapter<>(getActivity(), R.layout.list_item, mNetworks);
+ networksView.setAdapter(mNetworksAdapter);
+
+ setClickAction(view, R.id.networksRefresh, this::refreshNetworks);
+ setClickAction(view, R.id.networkRequestOemPaid, this::requestOemPaid);
+ setClickAction(view, R.id.networkRequestEth1, this::requestEth1);
+ setClickAction(view, R.id.networkReleaseNetwork, this::releaseNetworkRequest);
+
+ return view;
+ }
+
+ private void releaseNetworkRequest() {
+ mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+ showToast("Release request sent");
+ }
+
+ private void requestEth1() {
+ NetworkRequest request = new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .setNetworkSpecifier("eth1")
+ .build();
+ mConnectivityManager.requestNetwork(request, mNetworkCallback, mHandler);
+ }
+
+ private void requestOemPaid() {
+ NetworkRequest request = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)
+ .build();
+
+ mConnectivityManager.requestNetwork(request, mNetworkCallback, mHandler);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshNetworks();
+ }
+
+ private void setClickAction(View view, int id, Runnable action) {
+ view.findViewById(id).setOnClickListener(v -> action.run());
+ }
+
+ private void refreshNetworks() {
+ mNetworks.clear();
+
+ for (Network network : mConnectivityManager.getAllNetworks()) {
+ boolean isDefault = sameNetworkId(network, mConnectivityManager.getActiveNetwork());
+ NetworkCapabilities nc = mConnectivityManager.getNetworkCapabilities(network);
+ boolean isOemPaid = nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID);
+ boolean isInternet = nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+
+ NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(network);
+
+ mNetworks.add("netId: " + network.netId
+ + (isInternet ? " [INTERNET]" : "")
+ + (isDefault ? " [DEFAULT]" : "")
+ + (isOemPaid ? " [OEM-paid]" : "") + nc + " " + networkInfo);
+ }
+
+ mNetworksAdapter.notifyDataSetChanged();
+ }
+
+ private void showToast(String text) {
+ Log.d(TAG, "showToast: " + text);
+ Toast.makeText(getContext(), text, Toast.LENGTH_LONG).show();
+ }
+
+ private static boolean sameNetworkId(Network net1, Network net2) {
+ return net1 != null && net2 != null && net1.netId == net2.netId;
+
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java
index a1f8e1dece..c7b80e8462 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java
@@ -25,6 +25,7 @@ import android.car.hardware.hvac.CarHvacManager;
import android.hardware.automotive.vehicle.V2_0.VehicleAreaSeat;
import android.hardware.automotive.vehicle.V2_0.VehicleAreaWindow;
import android.os.Bundle;
+import android.os.Handler;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
@@ -76,6 +77,8 @@ public class HvacTestFragment extends Fragment {
private int mZoneForSetTempP;
private int mZoneForFanSpeed;
private int mZoneForFanPosition;
+ private List<CarPropertyConfig> mCarPropertyConfigs;
+ private View mHvacView;
private final CarHvacManager.CarHvacEventCallback mHvacCallback =
new CarHvacManager.CarHvacEventCallback () {
@@ -171,13 +174,9 @@ public class HvacTestFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
- mCarHvacManager = ((KitchenSinkActivity)getActivity()).getHvacManager();
+
super.onCreate(savedInstanceState);
- try {
- mCarHvacManager.registerCallback(mHvacCallback);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected!");
- }
+
}
@Override
@@ -188,77 +187,85 @@ public class HvacTestFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
- View v = inflater.inflate(R.layout.hvac_test, container, false);
-
- List<CarPropertyConfig> props;
- try {
- props = mCarHvacManager.getPropertyList();
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Failed to get list of properties", e);
- props = new ArrayList<>();
- }
+ mHvacView = inflater.inflate(R.layout.hvac_test, container, false);
+ final Runnable r = () -> {
+ mCarHvacManager = ((KitchenSinkActivity) getActivity()).getHvacManager();
+ try {
+ mCarHvacManager.registerCallback(mHvacCallback);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected!");
+ }
+ try {
+ mCarPropertyConfigs = mCarHvacManager.getPropertyList();
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Failed to get list of properties", e);
+ mCarPropertyConfigs = new ArrayList<>();
+ }
+ for (CarPropertyConfig prop : mCarPropertyConfigs) {
+ int propId = prop.getPropertyId();
- for(CarPropertyConfig prop : props) {
- int propId = prop.getPropertyId();
+ if (DBG) {
+ Log.d(TAG, prop.toString());
+ }
- if(DBG) {
- Log.d(TAG, prop.toString());
+ switch(propId) {
+ case CarHvacManager.ID_OUTSIDE_AIR_TEMP:
+ configureOutsideTemp(mHvacView, prop);
+ break;
+ case CarHvacManager.ID_ZONED_DUAL_ZONE_ON:
+ configureDualOn(mHvacView, prop);
+ break;
+ case CarHvacManager.ID_ZONED_AC_ON:
+ configureAcOn(mHvacView, prop);
+ break;
+ case CarHvacManager.ID_ZONED_FAN_DIRECTION:
+ configureFanPosition(mHvacView, prop);
+ break;
+ case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT:
+ configureFanSpeed(mHvacView, prop);
+ break;
+ case CarHvacManager.ID_ZONED_TEMP_SETPOINT:
+ configureTempSetpoint(mHvacView, prop);
+ break;
+ case CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON:
+ configureAutoModeOn(mHvacView, prop);
+ break;
+ case CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON:
+ configureRecircOn(mHvacView, prop);
+ break;
+ case CarHvacManager.ID_ZONED_MAX_AC_ON:
+ configureMaxAcOn(mHvacView, prop);
+ break;
+ case CarHvacManager.ID_ZONED_MAX_DEFROST_ON:
+ configureMaxDefrostOn(mHvacView, prop);
+ break;
+ case CarHvacManager.ID_WINDOW_DEFROSTER_ON:
+ configureDefrosterOn(mHvacView, prop);
+ break;
+ default:
+ Log.w(TAG, "propertyId " + propId + " is not handled");
+ break;
+ }
}
- switch(propId) {
- case CarHvacManager.ID_OUTSIDE_AIR_TEMP:
- configureOutsideTemp(v, prop);
- break;
- case CarHvacManager.ID_ZONED_DUAL_ZONE_ON:
- configureDualOn(v, prop);
- break;
- case CarHvacManager.ID_ZONED_AC_ON:
- configureAcOn(v, prop);
- break;
- case CarHvacManager.ID_ZONED_FAN_DIRECTION:
- configureFanPosition(v, prop);
- break;
- case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT:
- configureFanSpeed(v, prop);
- break;
- case CarHvacManager.ID_ZONED_TEMP_SETPOINT:
- configureTempSetpoint(v, prop);
- break;
- case CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON:
- configureAutoModeOn(v, prop);
- break;
- case CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON:
- configureRecircOn(v, prop);
- break;
- case CarHvacManager.ID_ZONED_MAX_AC_ON:
- configureMaxAcOn(v, prop);
- break;
- case CarHvacManager.ID_ZONED_MAX_DEFROST_ON:
- configureMaxDefrostOn(v, prop);
- break;
- case CarHvacManager.ID_WINDOW_DEFROSTER_ON:
- configureDefrosterOn(v, prop);
- break;
- default:
- Log.w(TAG, "propertyId " + propId + " is not handled");
- break;
- }
- }
+ mTvFanSpeed = (TextView) mHvacView.findViewById(R.id.tvFanSpeed);
+ mTvFanSpeed.setText(String.valueOf(mCurFanSpeed));
+ mTvDTemp = (TextView) mHvacView.findViewById(R.id.tvDTemp);
+ mTvDTemp.setText(String.valueOf(mCurDTemp));
+ mTvPTemp = (TextView) mHvacView.findViewById(R.id.tvPTemp);
+ mTvPTemp.setText(String.valueOf(mCurPTemp));
+ mTvOutsideTemp = (TextView) mHvacView.findViewById(R.id.tvOutsideTemp);
+ mTvOutsideTemp.setText("N/A");
+ };
- mTvFanSpeed = (TextView) v.findViewById(R.id.tvFanSpeed);
- mTvFanSpeed.setText(String.valueOf(mCurFanSpeed));
- mTvDTemp = (TextView) v.findViewById(R.id.tvDTemp);
- mTvDTemp.setText(String.valueOf(mCurDTemp));
- mTvPTemp = (TextView) v.findViewById(R.id.tvPTemp);
- mTvPTemp.setText(String.valueOf(mCurPTemp));
- mTvOutsideTemp = (TextView) v.findViewById(R.id.tvOutsideTemp);
- mTvOutsideTemp.setText("N/A");
+ ((KitchenSinkActivity) getActivity()).requestRefreshManager(r,
+ new Handler(getContext().getMainLooper()));
if(DBG) {
Log.d(TAG, "Starting HvacTestFragment");
}
- return v;
+ return mHvacView;
}
private void configureOutsideTemp(View v, CarPropertyConfig prop) {
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java
index fed1fbd322..9a6c2b92cf 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java
@@ -20,6 +20,7 @@ import android.car.CarNotConnectedException;
import android.car.hardware.power.CarPowerManager;
import android.content.Context;
import android.os.Bundle;
+import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
import android.support.v4.app.Fragment;
@@ -58,16 +59,20 @@ public class PowerTestFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
- mCarPowerManager = ((KitchenSinkActivity)getActivity()).getPowerManager();
- mExecutor = new ThreadPerTaskExecutor();
+ final Runnable r = () -> {
+ mCarPowerManager = ((KitchenSinkActivity) getActivity()).getPowerManager();
+ mExecutor = new ThreadPerTaskExecutor();
+ try {
+ mCarPowerManager.setListener(mPowerListener, mExecutor);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "CarPowerManager listener was not cleared");
+ }
+ };
+ ((KitchenSinkActivity) getActivity()).requestRefreshManager(r,
+ new Handler(getContext().getMainLooper()));
super.onCreate(savedInstanceState);
- try {
- mCarPowerManager.setListener(mPowerListener, mExecutor);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "CarPowerManager listener was not cleared");
- }
}
@Override
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java
index fc6621a7ca..ff1c402fea 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java
@@ -26,6 +26,7 @@ import android.content.DialogInterface.OnClickListener;
import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
import android.os.Bundle;
+import android.os.Handler;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
@@ -85,20 +86,23 @@ public class PropertyTestFragment extends Fragment implements OnItemSelectedList
mPropertyId = view.findViewById(R.id.sPropertyId);
mScrollView = view.findViewById(R.id.svEventLog);
mSetValue = view.findViewById(R.id.etSetPropertyValue);
-
- populateConfigList();
- mListView.setAdapter(new PropertyListAdapter(mPropInfo, mMgr, mEventLog, mScrollView,
- mActivity));
-
- // Configure dropdown menu for propertyId spinner
- ArrayAdapter<PropertyInfo> adapter =
- new ArrayAdapter<PropertyInfo>(mActivity, android.R.layout.simple_spinner_item,
- mPropInfo);
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- mPropertyId.setAdapter(adapter);
- mPropertyId.setOnItemSelectedListener(this);
-
-
+ mActivity = (KitchenSinkActivity) getActivity();
+
+ final Runnable r = () -> {
+ mMgr = mActivity.getPropertyManager();
+ populateConfigList();
+ mListView.setAdapter(new PropertyListAdapter(mPropInfo, mMgr, mEventLog, mScrollView,
+ mActivity));
+
+ // Configure dropdown menu for propertyId spinner
+ ArrayAdapter<PropertyInfo> adapter =
+ new ArrayAdapter<PropertyInfo>(mActivity, android.R.layout.simple_spinner_item,
+ mPropInfo);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mPropertyId.setAdapter(adapter);
+ mPropertyId.setOnItemSelectedListener(this);
+ };
+ mActivity.requestRefreshManager(r, new Handler(getContext().getMainLooper()));
// Configure listeners for buttons
Button b = view.findViewById(R.id.bGetProperty);
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
index abc2c10b49..1440ff0571 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
@@ -99,17 +99,19 @@ public class SensorsTestFragment extends Fragment {
View view = inflater.inflate(R.layout.sensors, container, false);
mActivity = (KitchenSinkActivity) getHost();
-
mSensorInfo = (TextView) view.findViewById(R.id.sensor_info);
mNaString = getContext().getString(R.string.sensor_na);
-
return view;
}
@Override
public void onResume() {
super.onResume();
- initPermissions();
+ final Runnable r = () -> {
+ initPermissions();
+ };
+ ((KitchenSinkActivity) getActivity()).requestRefreshManager(r,
+ new Handler(getContext().getMainLooper()));
}
@Override
@@ -207,6 +209,12 @@ public class SensorsTestFragment extends Fragment {
case CarSensorManager.SENSOR_TYPE_FUEL_DOOR_OPEN:
summary.add(getFuelDoorOpen(event));
break;
+ case CarSensorManager.SENSOR_TYPE_IGNITION_STATE:
+ summary.add(getContext().getString(R.string.sensor_ignition_status,
+ getTimestamp(event),
+ event == null ? mNaString :
+ event.getIgnitionStateData(null).ignitionState));
+ break;
case CarSensorManager.SENSOR_TYPE_PARKING_BRAKE:
summary.add(getContext().getString(R.string.sensor_parking_brake,
getTimestamp(event),
@@ -223,18 +231,15 @@ public class SensorsTestFragment extends Fragment {
getTimestamp(event),
event == null ? mNaString : event.getNightData(null).isNightMode));
break;
- case CarSensorManager.SENSOR_TYPE_ENVIRONMENT:
+ case CarSensorManager.SENSOR_TYPE_ENV_OUTSIDE_TEMPERATURE:
String temperature = mNaString;
- String pressure = mNaString;
if (event != null) {
CarSensorEvent.EnvironmentData env = event.getEnvironmentData(null);
temperature = Float.isNaN(env.temperature) ? temperature :
String.valueOf(env.temperature);
- pressure = Float.isNaN(env.pressure) ? pressure :
- String.valueOf(env.pressure);
}
summary.add(getContext().getString(R.string.sensor_environment,
- getTimestamp(event), temperature, pressure));
+ getTimestamp(event), temperature));
break;
case CarSensorManager.SENSOR_TYPE_WHEEL_TICK_DISTANCE:
if(event != null) {
diff --git a/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml
index 2be5a8302e..19a5552e2f 100644
--- a/tests/carservice_test/AndroidManifest.xml
+++ b/tests/carservice_test/AndroidManifest.xml
@@ -19,7 +19,10 @@
<uses-permission android:name="android.Manifest.permission.MODIFY_AUDIO_ROUTING" />
<uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
- <uses-permission android:name="android.car.permission.ADJUST_CAR_CABIN" />
+ <uses-permission android:name="android.car.permission.CONTROL_CAR_DOORS" />
+ <uses-permission android:name="android.car.permission.CONTROL_CAR_WINDOWS" />
+ <uses-permission android:name="android.car.permission.CONTROL_CAR_MIRRORS" />
+ <uses-permission android:name="android.car.permission.CONTROL_CAR_SEATS" />
<uses-permission android:name="android.car.permission.CAR_ENERGY" />
<uses-permission android:name="android.car.permission.CONTROL_APP_BLOCKING" />
<uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
diff --git a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
index a57d800b8d..c63c980fa4 100644
--- a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
@@ -173,7 +173,7 @@ public class MockedCarTestBase {
protected MockContext getCarServiceContext() throws NameNotFoundException {
if (mMockContext == null) {
mMockContext = new MockContext(getContext()
- .createPackageContext("com.android.car", Context.CONTEXT_IGNORE_SECURITY));
+ .createPackageContext("com.android.car.test", Context.CONTEXT_IGNORE_SECURITY));
}
return mMockContext;
}
@@ -393,6 +393,8 @@ public class MockedCarTestBase {
switch (name) {
case BLUETOOTH_SERVICE:
return CarServiceTestApp.getAppContext().getSystemService(name);
+ case AUDIO_SERVICE:
+ return CarServiceTestApp.getAppContext().getSystemService(name);
default:
return super.getSystemService(name);
}
diff --git a/tests/carservice_test/src/com/android/car/GarageModeTest.java b/tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java
index 23153fc7a4..8a9a90d125 100644
--- a/tests/carservice_test/src/com/android/car/GarageModeTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -13,12 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.car;
-import static org.junit.Assert.assertArrayEquals;
+package com.android.car.garagemode;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.car.settings.CarSettings;
@@ -31,16 +29,19 @@ import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
-import com.android.car.GarageModeService.GarageModePolicy;
-import com.android.car.GarageModeService.WakeupTime;
+import com.android.car.CarPowerManagementService;
+import com.android.car.DeviceIdleControllerWrapper;
+import com.android.car.R;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
+
@RunWith(AndroidJUnit4.class)
@MediumTest
-public class GarageModeTest {
- private static final int WAIT_FOR_COMPLETION_TIME = 3000;//ms
+public class GarageModeServiceTest {
+ private static final int WAIT_FOR_COMPLETION_TIME_MS = 3000;
@Test
@UiThreadTest
@@ -93,7 +94,7 @@ public class GarageModeTest {
powerManagementService.doNotifyPrepareShutdown(false);
assertTrue(garageMode.getGarageModeIndex() > 0);
powerManagementService.doNotifyPowerOn(true);
- assertEquals(0,garageMode.getGarageModeIndex());
+ assertEquals(0, garageMode.getGarageModeIndex());
}
@Test
@@ -111,11 +112,11 @@ public class GarageModeTest {
powerManagementService,
controller,
thread.getLooper());
- String[] policy = {
+ GarageModePolicy policy = new GarageModePolicy(new String[] {
"15m,1",
"6h,8",
"1d,5",
- };
+ });
SharedPreferences prefs =
getContext().getSharedPreferences("testPolicy", Context.MODE_PRIVATE);
prefs.edit().putInt("garage_mode_index", 0).apply();
@@ -125,7 +126,7 @@ public class GarageModeTest {
garageMode.onPrepareShutdown(false);
garageMode.onShutdown();
assertEquals(6 * 60 * 60, garageMode.getWakeupTime());
- Thread.sleep(WAIT_FOR_COMPLETION_TIME);
+ Thread.sleep(WAIT_FOR_COMPLETION_TIME_MS);
assertEquals(1, prefs.getInt("garage_mode_index", 0));
garageMode = new GarageModeServiceForTest(getContext(),
@@ -141,84 +142,78 @@ public class GarageModeTest {
garageMode.onPrepareShutdown(false);
garageMode.onShutdown();
assertEquals(24 * 60 * 60, garageMode.getWakeupTime());
- Thread.sleep(WAIT_FOR_COMPLETION_TIME);
+ Thread.sleep(WAIT_FOR_COMPLETION_TIME_MS);
assertEquals(9, prefs.getInt("garage_mode_index", 0));
}
@Test
public void testPolicyParserValid() throws Exception {
- WakeupTime expected[] = new WakeupTime[]{
- new WakeupTime(15 * 60, 1),
- new WakeupTime(6 * 60 * 60, 8),
- new WakeupTime(24 * 60 * 60, 5),
+ WakeupInterval[] expected = new WakeupInterval[] {
+ new WakeupInterval(15 * 60, 1),
+ new WakeupInterval(6 * 60 * 60, 8),
+ new WakeupInterval(24 * 60 * 60, 5),
};
- WakeupTime received[] = new GarageModePolicy(new String[] {
+ List<WakeupInterval> received = new GarageModePolicy(new String[] {
"15m,1",
"6h,8",
"1d,5",
- }).mWakeupTime;
+ }).getWakeupIntervals();
- assertEquals(expected.length, received.length);
+ assertEquals(expected.length, received.size());
for (int i = 0; i < expected.length; i++) {
- assertEquals(expected[i].mWakeupTime, received[i].mWakeupTime);
- assertEquals(expected[i].mNumAttempts, received[i].mNumAttempts);
+ assertEquals(expected[i].getWakeupInterval(), received.get(i).getWakeupInterval());
+ assertEquals(expected[i].getNumAttempts(), received.get(i).getNumAttempts());
}
}
- @Test(expected=RuntimeException.class)
- public void testPolicyParserNull() {
- new GarageModePolicy(null);
- }
- @Test(expected=RuntimeException.class)
- public void testPolicyParserEmptyArray() {
- new GarageModePolicy(new String[] {});
- }
- @Test(expected=RuntimeException.class)
- public void testPolicyParserEmptyString() {
- new GarageModePolicy(new String[] {""});
- }
- @Test(expected=RuntimeException.class)
- public void testPolicyParserMissingUnits() {
- new GarageModePolicy(new String[] {"15,1"});
- }
- @Test(expected=RuntimeException.class)
- public void testPolicyParserInvalidUnits() {
- new GarageModePolicy(new String[] {"15y,1"});
- }
- @Test(expected=RuntimeException.class)
- public void testPolicyParserNoCount() {
- new GarageModePolicy(new String[] {"15m"});
- }
- @Test(expected=RuntimeException.class)
- public void testPolicyParserBadCount() {
- new GarageModePolicy(new String[] {"15m,Q"});
- }
- @Test(expected=RuntimeException.class)
- public void testPolicyParserNegativeCount() {
- new GarageModePolicy(new String[] {"15m,-1"});
- }
- @Test(expected=RuntimeException.class)
- public void testPolicyParserNoTime() {
- new GarageModePolicy(new String[] {",1"});
- }
- @Test(expected=RuntimeException.class)
- public void testPolicyParserNoTimeValue() {
- new GarageModePolicy(new String[] {"m,1"});
- }
- @Test(expected=RuntimeException.class)
- public void testPolicyParserBadTime() {
- new GarageModePolicy(new String[] {"Qm,1"});
- }
- @Test(expected=RuntimeException.class)
- public void testPolicyParserNegativeTime() {
- new GarageModePolicy(new String[] {"-10m,1"});
+ @Test
+ public void testPolicyParser() {
+ GarageModePolicy policy;
+
+ policy = new GarageModePolicy(null);
+ assertEquals(0, policy.getWakeupIntervals().size());
+
+ policy = new GarageModePolicy(new String[] {});
+ assertEquals(0, policy.getWakeupIntervals().size());
+
+ policy = new GarageModePolicy(new String[] {""});
+ assertEquals(0, policy.getWakeupIntervals().size());
+
+ policy = new GarageModePolicy(new String[] {"15,1"});
+ assertEquals(0, policy.getWakeupIntervals().size());
+
+ policy = new GarageModePolicy(new String[] {"15y,1"});
+ assertEquals(0, policy.getWakeupIntervals().size());
+
+ policy = new GarageModePolicy(new String[] {"15m"});
+ assertEquals(0, policy.getWakeupIntervals().size());
+
+ policy = new GarageModePolicy(new String[] {"15m,Q"});
+ assertEquals(0, policy.getWakeupIntervals().size());
+
+ policy = new GarageModePolicy(new String[] {"15m,-1"});
+ assertEquals(0, policy.getWakeupIntervals().size());
+
+ policy = new GarageModePolicy(new String[] {",1"});
+ assertEquals(0, policy.getWakeupIntervals().size());
+
+ policy = new GarageModePolicy(new String[] {"m,1"});
+ assertEquals(0, policy.getWakeupIntervals().size());
+
+ policy = new GarageModePolicy(new String[] {"Qm,1"});
+ assertEquals(0, policy.getWakeupIntervals().size());
+
+ policy = new GarageModePolicy(new String[] {"-10m,1"});
+ assertEquals(0, policy.getWakeupIntervals().size());
+
}
@Test
public void testPolicyInResource() throws Exception {
// Test that the policy in the resource file parses fine.
- assertNotNull(new GarageModePolicy(getContext().getResources().getStringArray(
- R.array.config_garageModeCadence)).mWakeupTime);
+ GarageModePolicy policy = new GarageModePolicy(getContext().getResources().getStringArray(
+ R.array.config_garageModeCadence));
+ assertTrue(policy.getWakeupIntervals().size() > 0);
}
private static class MockCarPowerManagementService extends CarPowerManagementService {
@@ -232,14 +227,14 @@ public class GarageModeTest {
}
private static class GarageModeServiceForTest extends GarageModeService {
- public GarageModeServiceForTest(Context context,
+ GarageModeServiceForTest(Context context,
CarPowerManagementService powerManagementService,
DeviceIdleControllerWrapper controllerWrapper,
Looper looper) {
super(context, powerManagementService, controllerWrapper, looper);
}
- public GarageModeServiceForTest(Context context,
+ GarageModeServiceForTest(Context context,
CarPowerManagementService powerManagementService,
DeviceIdleControllerWrapper controllerWrapper) {
super(context, powerManagementService, controllerWrapper, Looper.myLooper());
diff --git a/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
index 8ac7d9b1db..b071e9947d 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
@@ -31,6 +31,7 @@ import android.car.hardware.CarSensorEvent;
import android.car.hardware.CarSensorManager;
import android.car.hardware.property.CarPropertyEvent;
import android.car.hardware.property.ICarPropertyEventListener;
+import android.car.user.CarUserManagerHelper;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -72,7 +73,9 @@ import java.util.stream.Collectors;
* The following mocks are used:
* 1. {@link Context} provides files and a mocked {@link LocationManager}.
* 2. {@link LocationManager} provides dummy {@link Location}s.
- * 3. {@link CarSensorService} registers a handler for sensor events and sends ignition-off events.
+ * 3. {@link CarPropertyService} registers a listener for ignition state events.
+ * 3. {@link CarPowerManagementService} registers a handler for power events.
+ * 4. {@link CarUserManagerHelper} tells whether or not the system user is headless.
*/
@RunWith(AndroidJUnit4.class)
public class CarLocationServiceTest {
@@ -85,6 +88,7 @@ public class CarLocationServiceTest {
@Mock private LocationManager mMockLocationManager;
@Mock private CarPropertyService mMockCarPropertyService;
@Mock private CarPowerManagementService mMockCarPowerManagementService;
+ @Mock private CarUserManagerHelper mMockCarUserManagerHelper;
/**
* Initialize all of the objects with the @Mock annotation.
@@ -95,7 +99,7 @@ public class CarLocationServiceTest {
mContext = InstrumentationRegistry.getTargetContext();
mLatch = new CountDownLatch(1);
mCarLocationService = new CarLocationService(mMockContext, mMockCarPowerManagementService,
- mMockCarPropertyService) {
+ mMockCarPropertyService, mMockCarUserManagerHelper) {
@Override
void asyncOperation(Runnable operation) {
super.asyncOperation(() -> {
@@ -143,12 +147,13 @@ public class CarLocationServiceTest {
mCarLocationService);
verify(mMockContext).registerReceiver(eq(mCarLocationService), argument.capture());
IntentFilter intentFilter = argument.getValue();
- assertEquals(3, intentFilter.countActions());
+ assertEquals(4, intentFilter.countActions());
String[] actions = {intentFilter.getAction(0), intentFilter.getAction(1),
- intentFilter.getAction(2)};
+ intentFilter.getAction(2), intentFilter.getAction(3)};
assertTrue(ArrayUtils.contains(actions, Intent.ACTION_LOCKED_BOOT_COMPLETED));
assertTrue(ArrayUtils.contains(actions, LocationManager.MODE_CHANGED_ACTION));
assertTrue(ArrayUtils.contains(actions, LocationManager.GPS_ENABLED_CHANGE_ACTION));
+ assertTrue(ArrayUtils.contains(actions, Intent.ACTION_USER_SWITCHED));
verify(mMockCarPropertyService).registerListener(
eq(CarSensorManager.SENSOR_TYPE_IGNITION_STATE), eq(0.0f), any());
}
@@ -166,10 +171,11 @@ public class CarLocationServiceTest {
/**
* Test that the {@link CarLocationService} parses a location from a JSON serialization and then
- * injects it into the {@link LocationManager} upon boot complete.
+ * injects it into the {@link LocationManager} upon boot complete if the system user is not
+ * headless.
*/
@Test
- public void testLoadsLocation() throws IOException, InterruptedException {
+ public void testLoadsLocationOnLockedBootComplete() throws IOException, InterruptedException {
long currentTime = System.currentTimeMillis();
long elapsedTime = SystemClock.elapsedRealtimeNanos();
long pastTime = currentTime - 60000;
@@ -181,6 +187,7 @@ public class CarLocationServiceTest {
when(mMockLocationManager.injectLocation(argument.capture())).thenReturn(true);
when(mMockContext.getFileStreamPath("location_cache.json"))
.thenReturn(mContext.getFileStreamPath(TEST_FILENAME));
+ when(mMockCarUserManagerHelper.isHeadlessSystemUser()).thenReturn(false);
mCarLocationService.onReceive(mMockContext,
new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
@@ -196,6 +203,39 @@ public class CarLocationServiceTest {
}
/**
+ * Test that the {@link CarLocationService} parses a location from a JSON seialization and then
+ * injects it into the {@link LocationManager} upon user switch if the system user is headless.
+ */
+ @Test
+ public void testLoadsLocationWithHeadlessSystemUser() throws IOException, InterruptedException {
+ long currentTime = System.currentTimeMillis();
+ long elapsedTime = SystemClock.elapsedRealtimeNanos();
+ long pastTime = currentTime - 60000;
+ writeCacheFile("{\"provider\": \"gps\", \"latitude\": 16.7666, \"longitude\": 3.0026,"
+ + "\"accuracy\":12.3, \"captureTime\": " + pastTime + "}");
+ ArgumentCaptor<Location> argument = ArgumentCaptor.forClass(Location.class);
+ when(mMockContext.getSystemService(Context.LOCATION_SERVICE))
+ .thenReturn(mMockLocationManager);
+ when(mMockLocationManager.injectLocation(argument.capture())).thenReturn(true);
+ when(mMockContext.getFileStreamPath("location_cache.json"))
+ .thenReturn(mContext.getFileStreamPath(TEST_FILENAME));
+ when(mMockCarUserManagerHelper.isHeadlessSystemUser()).thenReturn(true);
+
+ Intent userSwitchedIntent = new Intent(Intent.ACTION_USER_SWITCHED);
+ userSwitchedIntent.putExtra(Intent.EXTRA_USER_HANDLE, 11);
+ mCarLocationService.onReceive(mMockContext, userSwitchedIntent);
+ mLatch.await();
+
+ Location location = argument.getValue();
+ assertEquals("gps", location.getProvider());
+ assertEquals(16.7666, location.getLatitude());
+ assertEquals(3.0026, location.getLongitude());
+ assertEquals(12.3f, location.getAccuracy());
+ assertTrue(location.getTime() >= currentTime);
+ assertTrue(location.getElapsedRealtimeNanos() >= elapsedTime);
+ }
+
+ /**
* Test that the {@link CarLocationService} does not inject a location if there is no location
* cache file.
*/
diff --git a/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java b/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
index 44cc0d6ae5..7f53da5655 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
@@ -18,8 +18,12 @@ package com.android.car;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.car.user.CarUserManagerHelper;
@@ -30,11 +34,13 @@ import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.Handler;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Before;
@@ -42,6 +48,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@@ -54,10 +61,11 @@ import java.util.List;
* The following mocks are used:
* 1. {@link Context} provides system services and resources.
* 2. {@link UserManager} provides dummy users and user info.
- * 3. {@link ActivityManager} provides dummy current process user.
+ * 3. {@link ActivityManager} to verify user switch is invoked.
* 4. {@link CarUserManagerHelper.OnUsersUpdateListener} registers a listener for user updates.
*/
@RunWith(AndroidJUnit4.class)
+@SmallTest
public class CarUserManagerHelperTest {
@Mock
private Context mContext;
@@ -68,25 +76,37 @@ public class CarUserManagerHelperTest {
@Mock
private CarUserManagerHelper.OnUsersUpdateListener mTestListener;
- private CarUserManagerHelper mHelper;
+ private CarUserManagerHelper mCarUserManagerHelper;
private UserInfo mCurrentProcessUser;
private UserInfo mSystemUser;
private String mGuestUserName = "testGuest";
private String mTestUserName = "testUser";
+ private int mForegroundUserId;
+ private UserInfo mForegroundUser;
@Before
public void setUpMocksAndVariables() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
- when(mContext.getSystemService(Context.ACTIVITY_SERVICE)).thenReturn(mActivityManager);
- when(mContext.getResources())
- .thenReturn(InstrumentationRegistry.getTargetContext().getResources());
- when(mContext.getApplicationContext()).thenReturn(mContext);
- mHelper = new CarUserManagerHelper(mContext);
+ doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE);
+ doReturn(mActivityManager).when(mContext).getSystemService(Context.ACTIVITY_SERVICE);
+ doReturn(InstrumentationRegistry.getTargetContext().getResources())
+ .when(mContext).getResources();
+ doReturn(mContext).when(mContext).getApplicationContext();
+ mCarUserManagerHelper = new CarUserManagerHelper(mContext);
mCurrentProcessUser = createUserInfoForId(UserHandle.myUserId());
mSystemUser = createUserInfoForId(UserHandle.USER_SYSTEM);
- when(mUserManager.getUserInfo(UserHandle.myUserId())).thenReturn(mCurrentProcessUser);
+ doReturn(mCurrentProcessUser).when(mUserManager).getUserInfo(UserHandle.myUserId());
+
+ // Get the ID of the foreground user running this test.
+ // We cannot mock the foreground user since getCurrentUser is static.
+ // We cannot rely on foreground_id != system_id, they could be the same user.
+ mForegroundUserId = ActivityManager.getCurrentUser();
+ mForegroundUser = createUserInfoForId(mForegroundUserId);
+
+ // Restore the non-headless state before every test. Individual tests can set the property
+ // to true to test the headless system user scenario.
+ SystemProperties.set("android.car.systemuser.headless", "false");
}
@Test
@@ -94,10 +114,10 @@ public class CarUserManagerHelperTest {
UserInfo testInfo = new UserInfo();
testInfo.id = UserHandle.USER_SYSTEM;
- assertThat(mHelper.isSystemUser(testInfo)).isTrue();
+ assertThat(mCarUserManagerHelper.isSystemUser(testInfo)).isTrue();
testInfo.id = UserHandle.USER_SYSTEM + 2; // Make it different than system id.
- assertThat(mHelper.isSystemUser(testInfo)).isFalse();
+ assertThat(mCarUserManagerHelper.isSystemUser(testInfo)).isFalse();
}
// System user will not be returned when calling get all users.
@@ -108,92 +128,67 @@ public class CarUserManagerHelperTest {
UserInfo otherUser2 = createUserInfoForId(11);
UserInfo otherUser3 = createUserInfoForId(12);
- List<UserInfo> testUsers = new ArrayList<>();
- testUsers.add(mSystemUser);
- testUsers.add(otherUser1);
- testUsers.add(otherUser2);
- testUsers.add(otherUser3);
+ mockGetUsers(mSystemUser, otherUser1, otherUser2, otherUser3);
- when(mUserManager.getUsers(true)).thenReturn(testUsers);
-
- // Should return 3 users that don't have SYSTEM USER id.
- assertThat(mHelper.getAllUsers()).hasSize(3);
- assertThat(mHelper.getAllUsers())
- .containsExactly(otherUser1, otherUser2, otherUser3);
+ assertThat(mCarUserManagerHelper.getAllUsers())
+ .containsExactly(otherUser1, otherUser2, otherUser3);
}
@Test
- public void testHeadlessUser0GetAllUsersWithActiveForegroundUser_NotReturnSystemUser() {
- SystemProperties.set("android.car.systemuser.headless", "true");
- mCurrentProcessUser = createUserInfoForId(10);
-
- UserInfo otherUser1 = createUserInfoForId(11);
- UserInfo otherUser2 = createUserInfoForId(12);
- UserInfo otherUser3 = createUserInfoForId(13);
+ public void testGetAllSwitchableUsers() {
+ // Create two non-foreground users.
+ UserInfo user1 = createUserInfoForId(mForegroundUserId + 1);
+ UserInfo user2 = createUserInfoForId(mForegroundUserId + 2);
- List<UserInfo> testUsers = new ArrayList<>();
- testUsers.add(mSystemUser);
- testUsers.add(mCurrentProcessUser);
- testUsers.add(otherUser1);
- testUsers.add(otherUser2);
- testUsers.add(otherUser3);
+ mockGetUsers(mForegroundUser, user1, user2);
- when(mUserManager.getUsers(true)).thenReturn(testUsers);
+ // Should return all non-foreground users.
+ assertThat(mCarUserManagerHelper.getAllSwitchableUsers()).containsExactly(user1, user2);
+ }
- assertThat(mHelper.getAllUsers().size()).isEqualTo(4);
- assertThat(mHelper.getAllUsers())
- .containsExactly(mCurrentProcessUser, otherUser1, otherUser2, otherUser3);
+ @Test
+ public void testGetAllPersistentUsers() {
+ // Create two non-ephemeral users.
+ UserInfo user1 = createUserInfoForId(mForegroundUserId);
+ UserInfo user2 = createUserInfoForId(mForegroundUserId + 1);
+ // Create two ephemeral users.
+ UserInfo user3 = new UserInfo(
+ /* id= */mForegroundUserId + 2, /* name = */ "user3", UserInfo.FLAG_EPHEMERAL);
+ UserInfo user4 = new UserInfo(
+ /* id= */mForegroundUserId + 3, /* name = */ "user4", UserInfo.FLAG_EPHEMERAL);
+
+ mockGetUsers(user1, user2, user3, user4);
+
+ // Should return all non-ephemeral users.
+ assertThat(mCarUserManagerHelper.getAllPersistentUsers()).containsExactly(user1, user2);
}
@Test
- public void testGetAllSwitchableUsers() {
- UserInfo user1 = createUserInfoForId(10);
+ public void testGetAllAdminUsers() {
+ // Create two admin, and two non-admin users.
+ UserInfo user1 = new UserInfo(/* id= */ 10, /* name = */ "user10", UserInfo.FLAG_ADMIN);
UserInfo user2 = createUserInfoForId(11);
UserInfo user3 = createUserInfoForId(12);
+ UserInfo user4 = new UserInfo(/* id= */ 13, /* name = */ "user13", UserInfo.FLAG_ADMIN);
- List<UserInfo> testUsers = new ArrayList<>();
- testUsers.add(mSystemUser);
- testUsers.add(user1);
- testUsers.add(user2);
- testUsers.add(user3);
-
- when(mUserManager.getUsers(true)).thenReturn(new ArrayList<>(testUsers));
-
- // Should return all 3 non-system users.
- assertThat(mHelper.getAllUsers().size())
- .isEqualTo(3);
+ mockGetUsers(user1, user2, user3, user4);
- when(mUserManager.getUserInfo(UserHandle.myUserId())).thenReturn(user1);
- // Should return user 10, 11 and 12.
- assertThat(mHelper.getAllSwitchableUsers().size())
- .isEqualTo(3);
- assertThat(mHelper.getAllSwitchableUsers()).contains(user1);
- assertThat(mHelper.getAllSwitchableUsers()).contains(user2);
- assertThat(mHelper.getAllSwitchableUsers()).contains(user3);
+ // Should return only admin users.
+ assertThat(mCarUserManagerHelper.getAllAdminUsers()).containsExactly(user1, user4);
}
- // Get all users for headless user 0 model should exclude system user by default.
@Test
- public void testHeadlessUser0GetAllSwitchableUsers() {
- SystemProperties.set("android.car.systemuser.headless", "true");
+ public void testGetAllUsersExceptGuests() {
+ // Create two users and a guest user.
UserInfo user1 = createUserInfoForId(10);
- UserInfo user2 = createUserInfoForId(11);
- UserInfo user3 = createUserInfoForId(12);
-
- List<UserInfo> testUsers = new ArrayList<>();
- testUsers.add(mSystemUser);
- testUsers.add(user1);
- testUsers.add(user2);
- testUsers.add(user3);
+ UserInfo user2 = createUserInfoForId(12);
+ UserInfo user3 = new UserInfo(/* id= */ 13, /* name = */ "user13", UserInfo.FLAG_GUEST);
- when(mUserManager.getUsers(true)).thenReturn(new ArrayList<>(testUsers));
+ mockGetUsers(user1, user2, user3);
- // Should return all 3 non-system users.
- assertThat(mHelper.getAllUsers()).hasSize(3);
-
- when(mUserManager.getUserInfo(UserHandle.myUserId())).thenReturn(user1);
- // Should return user 10, 11 and 12.
- assertThat(mHelper.getAllSwitchableUsers()).containsExactly(user1, user2, user3);
+ // Should not return guests.
+ assertThat(mCarUserManagerHelper.getAllUsersExceptGuests())
+ .containsExactly(user1, user2);
}
@Test
@@ -202,127 +197,304 @@ public class CarUserManagerHelperTest {
// System user cannot be removed.
testInfo.id = UserHandle.USER_SYSTEM;
- assertThat(mHelper.canUserBeRemoved(testInfo)).isFalse();
+ assertThat(mCarUserManagerHelper.canUserBeRemoved(testInfo)).isFalse();
testInfo.id = UserHandle.USER_SYSTEM + 2; // Make it different than system id.
- assertThat(mHelper.canUserBeRemoved(testInfo)).isTrue();
+ assertThat(mCarUserManagerHelper.canUserBeRemoved(testInfo)).isTrue();
}
@Test
public void testCurrentProcessCanAddUsers() {
- when(mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)).thenReturn(false);
- assertThat(mHelper.canCurrentProcessAddUsers()).isTrue();
+ doReturn(false).when(mUserManager)
+ .hasUserRestriction(UserManager.DISALLOW_ADD_USER);
+ assertThat(mCarUserManagerHelper.canCurrentProcessAddUsers()).isTrue();
- when(mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)).thenReturn(true);
- assertThat(mHelper.canCurrentProcessAddUsers()).isFalse();
+ doReturn(true).when(mUserManager)
+ .hasUserRestriction(UserManager.DISALLOW_ADD_USER);
+ assertThat(mCarUserManagerHelper.canCurrentProcessAddUsers()).isFalse();
}
@Test
public void testCurrentProcessCanRemoveUsers() {
- when(mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)).thenReturn(false);
- assertThat(mHelper.canCurrentProcessRemoveUsers()).isTrue();
+ doReturn(false).when(mUserManager)
+ .hasUserRestriction(UserManager.DISALLOW_REMOVE_USER);
+ assertThat(mCarUserManagerHelper.canCurrentProcessRemoveUsers()).isTrue();
- when(mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)).thenReturn(true);
- assertThat(mHelper.canCurrentProcessRemoveUsers()).isFalse();
+ doReturn(true).when(mUserManager)
+ .hasUserRestriction(UserManager.DISALLOW_REMOVE_USER);
+ assertThat(mCarUserManagerHelper.canCurrentProcessRemoveUsers()).isFalse();
}
@Test
public void testCurrentProcessCanSwitchUsers() {
- when(mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)).thenReturn(false);
- assertThat(mHelper.canCurrentProcessSwitchUsers()).isTrue();
+ doReturn(false).when(mUserManager)
+ .hasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
+ assertThat(mCarUserManagerHelper.canCurrentProcessSwitchUsers()).isTrue();
- when(mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)).thenReturn(true);
- assertThat(mHelper.canCurrentProcessSwitchUsers()).isFalse();
+ doReturn(true).when(mUserManager)
+ .hasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
+ assertThat(mCarUserManagerHelper.canCurrentProcessSwitchUsers()).isFalse();
}
@Test
public void testCurrentGuestProcessCannotModifyAccounts() {
- assertThat(mHelper.canCurrentProcessModifyAccounts()).isTrue();
+ assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isTrue();
+
+ doReturn(true).when(mUserManager).isGuestUser();
- when(mUserManager.isGuestUser()).thenReturn(true);
- assertThat(mHelper.canCurrentProcessModifyAccounts()).isFalse();
+ assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isFalse();
}
@Test
public void testCurrentDemoProcessCannotModifyAccounts() {
- assertThat(mHelper.canCurrentProcessModifyAccounts()).isTrue();
+ assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isTrue();
+
+ doReturn(true).when(mUserManager).isDemoUser();
- when(mUserManager.isDemoUser()).thenReturn(true);
- assertThat(mHelper.canCurrentProcessModifyAccounts()).isFalse();
+ assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isFalse();
}
@Test
public void testCurrentDisallowModifyAccountsProcessIsEnforced() {
- assertThat(mHelper.canCurrentProcessModifyAccounts()).isTrue();
+ assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isTrue();
+
+ doReturn(true).when(mUserManager)
+ .hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS);
+
+ assertThat(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).isFalse();
+ }
+
+ @Test
+ public void testGetMaxSupportedUsers() {
+ SystemProperties.set("fw.max_users", "11");
+
+ assertThat(mCarUserManagerHelper.getMaxSupportedUsers()).isEqualTo(11);
+
+ // In headless user 0 model, we want to exclude the system user.
+ SystemProperties.set("android.car.systemuser.headless", "true");
+ assertThat(mCarUserManagerHelper.getMaxSupportedUsers()).isEqualTo(10);
+ }
+
+ @Test
+ public void testGetMaxSupportedRealUsers() {
+ SystemProperties.set("fw.max_users", "7");
+
+ // Create three managed profiles, and two normal users.
+ UserInfo user1 = createUserInfoForId(10);
+ UserInfo user2 =
+ new UserInfo(/* id= */ 11, /* name = */ "user11", UserInfo.FLAG_MANAGED_PROFILE);
+ UserInfo user3 =
+ new UserInfo(/* id= */ 12, /* name = */ "user12", UserInfo.FLAG_MANAGED_PROFILE);
+ UserInfo user4 = createUserInfoForId(13);
+ UserInfo user5 =
+ new UserInfo(/* id= */ 14, /* name = */ "user14", UserInfo.FLAG_MANAGED_PROFILE);
+
+ mockGetUsers(user1, user2, user3, user4, user5);
+
+ // Max users - # managed profiles.
+ assertThat(mCarUserManagerHelper.getMaxSupportedRealUsers()).isEqualTo(4);
+ }
+
+ @Test
+ public void testIsUserLimitReached() {
+ UserInfo user1 = createUserInfoForId(10);
+ UserInfo user2 =
+ new UserInfo(/* id= */ 11, /* name = */ "user11", UserInfo.FLAG_MANAGED_PROFILE);
+ UserInfo user3 =
+ new UserInfo(/* id= */ 12, /* name = */ "user12", UserInfo.FLAG_MANAGED_PROFILE);
+ UserInfo user4 = createUserInfoForId(13);
- when(mUserManager.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS))
- .thenReturn(true);
- assertThat(mHelper.canCurrentProcessModifyAccounts()).isFalse();
+ mockGetUsers(user1, user2, user3, user4);
+
+ SystemProperties.set("fw.max_users", "5");
+ assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
+
+ SystemProperties.set("fw.max_users", "4");
+ assertThat(mCarUserManagerHelper.isUserLimitReached()).isTrue();
+ }
+
+ @Test
+ public void testHeadlessSystemUser_IsUserLimitReached() {
+ SystemProperties.set("android.car.systemuser.headless", "true");
+ UserInfo user1 = createUserInfoForId(10);
+ UserInfo user2 =
+ new UserInfo(/* id= */ 11, /* name = */ "user11", UserInfo.FLAG_MANAGED_PROFILE);
+ UserInfo user3 =
+ new UserInfo(/* id= */ 12, /* name = */ "user12", UserInfo.FLAG_MANAGED_PROFILE);
+ UserInfo user4 = createUserInfoForId(13);
+
+ mockGetUsers(mSystemUser, user1, user2, user3, user4);
+
+ SystemProperties.set("fw.max_users", "6");
+ assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
+
+ SystemProperties.set("fw.max_users", "5");
+ assertThat(mCarUserManagerHelper.isUserLimitReached()).isTrue();
+ }
+
+ @Test
+ public void testIsUserLimitReachedIgnoresGuests() {
+ SystemProperties.set("fw.max_users", "5");
+
+ UserInfo user1 = createUserInfoForId(10);
+ UserInfo user2 =
+ new UserInfo(/* id= */ 11, /* name = */ "user11", UserInfo.FLAG_MANAGED_PROFILE);
+ UserInfo user3 =
+ new UserInfo(/* id= */ 12, /* name = */ "user12", UserInfo.FLAG_MANAGED_PROFILE);
+ UserInfo user4 = createUserInfoForId(13);
+ UserInfo user5 = new UserInfo(/* id= */ 14, /* name = */ "user14", UserInfo.FLAG_GUEST);
+ UserInfo user6 = createUserInfoForId(15);
+
+ mockGetUsers(user1, user2, user3, user4);
+ assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
+
+ // Add guest user. Verify it doesn't affect the limit.
+ mockGetUsers(user1, user2, user3, user4, user5);
+ assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
+
+ // Add normal user. Limit is reached
+ mockGetUsers(user1, user2, user3, user4, user5, user6);
+ assertThat(mCarUserManagerHelper.isUserLimitReached()).isTrue();
}
@Test
public void testCreateNewAdminUser() {
+ // Make sure current user is admin, since only admins can create other admins.
+ doReturn(true).when(mUserManager).isAdminUser();
+
// Verify createUser on UserManager gets called.
- mHelper.createNewAdminUser(mTestUserName);
+ mCarUserManagerHelper.createNewAdminUser(mTestUserName);
verify(mUserManager).createUser(mTestUserName, UserInfo.FLAG_ADMIN);
- when(mUserManager.createUser(mTestUserName, UserInfo.FLAG_ADMIN)).thenReturn(null);
- assertThat(mHelper.createNewAdminUser(mTestUserName)).isNull();
+ doReturn(null).when(mUserManager).createUser(mTestUserName, UserInfo.FLAG_ADMIN);
+ assertThat(mCarUserManagerHelper.createNewAdminUser(mTestUserName)).isNull();
UserInfo newUser = new UserInfo();
newUser.name = mTestUserName;
- when(mUserManager.createUser(mTestUserName, UserInfo.FLAG_ADMIN)).thenReturn(newUser);
- assertThat(mHelper.createNewAdminUser(mTestUserName)).isEqualTo(newUser);
+ doReturn(newUser).when(mUserManager).createUser(mTestUserName, UserInfo.FLAG_ADMIN);
+ assertThat(mCarUserManagerHelper.createNewAdminUser(mTestUserName)).isEqualTo(newUser);
+ }
+
+ @Test
+ public void testAdminsCanCreateAdmins() {
+ String newAdminName = "Test new admin";
+ UserInfo expectedAdmin = new UserInfo();
+ expectedAdmin.name = newAdminName;
+ doReturn(expectedAdmin).when(mUserManager).createUser(newAdminName, UserInfo.FLAG_ADMIN);
+
+ // Admins can create other admins.
+ doReturn(true).when(mUserManager).isAdminUser();
+ UserInfo actualAdmin = mCarUserManagerHelper.createNewAdminUser(newAdminName);
+ assertThat(actualAdmin).isEqualTo(expectedAdmin);
+ }
+
+ @Test
+ public void testNonAdminsCanNotCreateAdmins() {
+ String newAdminName = "Test new admin";
+ UserInfo expectedAdmin = new UserInfo();
+ expectedAdmin.name = newAdminName;
+ doReturn(expectedAdmin).when(mUserManager).createUser(newAdminName, UserInfo.FLAG_ADMIN);
+
+ // Test that non-admins cannot create new admins.
+ doReturn(false).when(mUserManager).isAdminUser(); // Current user non-admin.
+ assertThat(mCarUserManagerHelper.createNewAdminUser(newAdminName)).isNull();
+ }
+
+ @Test
+ public void testSystemUserCanCreateAdmins() {
+ String newAdminName = "Test new admin";
+ UserInfo expectedAdmin = new UserInfo();
+ expectedAdmin.name = newAdminName;
+
+ doReturn(expectedAdmin).when(mUserManager).createUser(newAdminName, UserInfo.FLAG_ADMIN);
+
+ // System user can create admins.
+ doReturn(true).when(mUserManager).isSystemUser();
+ UserInfo actualAdmin = mCarUserManagerHelper.createNewAdminUser(newAdminName);
+ assertThat(actualAdmin).isEqualTo(expectedAdmin);
}
@Test
public void testCreateNewNonAdminUser() {
// Verify createUser on UserManager gets called.
- mHelper.createNewNonAdminUser(mTestUserName);
+ mCarUserManagerHelper.createNewNonAdminUser(mTestUserName);
verify(mUserManager).createUser(mTestUserName, 0);
- when(mUserManager.createUser(mTestUserName, 0)).thenReturn(null);
- assertThat(mHelper.createNewNonAdminUser(mTestUserName)).isNull();
+ doReturn(null).when(mUserManager).createUser(mTestUserName, 0);
+ assertThat(mCarUserManagerHelper.createNewNonAdminUser(mTestUserName)).isNull();
UserInfo newUser = new UserInfo();
newUser.name = mTestUserName;
- when(mUserManager.createUser(mTestUserName, 0)).thenReturn(newUser);
- assertThat(mHelper.createNewNonAdminUser(mTestUserName)).isEqualTo(newUser);
+ doReturn(newUser).when(mUserManager).createUser(mTestUserName, 0);
+ assertThat(mCarUserManagerHelper.createNewNonAdminUser(mTestUserName)).isEqualTo(newUser);
+ }
+
+ @Test
+ public void testCannotRemoveSystemUser() {
+ assertThat(mCarUserManagerHelper.removeUser(mSystemUser, mGuestUserName)).isFalse();
+ }
+
+ @Test
+ public void testAdminsCanRemoveOtherUsers() {
+ int idToRemove = mCurrentProcessUser.id + 2;
+ UserInfo userToRemove = createUserInfoForId(idToRemove);
+
+ doReturn(true).when(mUserManager).removeUser(idToRemove);
+
+ // If Admin is removing non-current, non-system user, simply calls removeUser.
+ doReturn(true).when(mUserManager).isAdminUser();
+ assertThat(mCarUserManagerHelper.removeUser(userToRemove, mGuestUserName)).isTrue();
+ verify(mUserManager).removeUser(idToRemove);
+ }
+
+ @Test
+ public void testNonAdminsCanNotRemoveOtherUsers() {
+ UserInfo otherUser = createUserInfoForId(mCurrentProcessUser.id + 2);
+
+ // Make current user non-admin.
+ doReturn(false).when(mUserManager).isAdminUser();
+
+ // Mock so that removeUser always pretends it's successful.
+ doReturn(true).when(mUserManager).removeUser(anyInt());
+
+ // If Non-Admin is trying to remove someone other than themselves, they should fail.
+ assertThat(mCarUserManagerHelper.removeUser(otherUser, mGuestUserName)).isFalse();
+ verify(mUserManager, never()).removeUser(otherUser.id);
}
@Test
- public void testRemoveUser() {
+ public void testRemoveLastActiveUser() {
// Cannot remove system user.
- assertThat(mHelper.removeUser(mSystemUser, mGuestUserName)).isFalse();
+ assertThat(mCarUserManagerHelper.removeUser(mSystemUser, mGuestUserName)).isFalse();
- // Removing non-current, non-system user, simply calls removeUser.
- UserInfo userToRemove = createUserInfoForId(mCurrentProcessUser.id + 2);
+ UserInfo adminInfo = new UserInfo(/* id= */10, "admin", UserInfo.FLAG_ADMIN);
+ mockGetUsers(adminInfo);
- mHelper.removeUser(userToRemove, mGuestUserName);
- verify(mUserManager).removeUser(mCurrentProcessUser.id + 2);
+ assertThat(mCarUserManagerHelper.removeUser(adminInfo, mGuestUserName))
+ .isEqualTo(false);
}
@Test
public void testSwitchToGuest() {
- mHelper.startNewGuestSession(mGuestUserName);
+ mCarUserManagerHelper.startNewGuestSession(mGuestUserName);
verify(mUserManager).createGuest(mContext, mGuestUserName);
- UserInfo guestInfo = new UserInfo(21, mGuestUserName, UserInfo.FLAG_GUEST);
- when(mUserManager.createGuest(mContext, mGuestUserName)).thenReturn(guestInfo);
- mHelper.startNewGuestSession(mGuestUserName);
+ UserInfo guestInfo = new UserInfo(/* id= */21, mGuestUserName, UserInfo.FLAG_GUEST);
+ doReturn(guestInfo).when(mUserManager).createGuest(mContext, mGuestUserName);
+ mCarUserManagerHelper.startNewGuestSession(mGuestUserName);
verify(mActivityManager).switchUser(21);
}
@Test
public void testGetUserIcon() {
- mHelper.getUserIcon(mCurrentProcessUser);
+ mCarUserManagerHelper.getUserIcon(mCurrentProcessUser);
verify(mUserManager).getUserIcon(mCurrentProcessUser.id);
}
@Test
public void testScaleUserIcon() {
Bitmap fakeIcon = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
- Drawable scaledIcon = mHelper.scaleUserIcon(fakeIcon, 300);
+ Drawable scaledIcon = mCarUserManagerHelper.scaleUserIcon(fakeIcon, 300);
assertThat(scaledIcon.getIntrinsicWidth()).isEqualTo(300);
assertThat(scaledIcon.getIntrinsicHeight()).isEqualTo(300);
}
@@ -331,13 +503,107 @@ public class CarUserManagerHelperTest {
public void testSetUserName() {
UserInfo testInfo = createUserInfoForId(mCurrentProcessUser.id + 3);
String newName = "New Test Name";
- mHelper.setUserName(testInfo, newName);
+ mCarUserManagerHelper.setUserName(testInfo, newName);
verify(mUserManager).setUserName(mCurrentProcessUser.id + 3, newName);
}
@Test
+ public void testIsCurrentProcessSystemUser() {
+ doReturn(true).when(mUserManager).isAdminUser();
+ assertThat(mCarUserManagerHelper.isCurrentProcessAdminUser()).isTrue();
+
+ doReturn(false).when(mUserManager).isAdminUser();
+ assertThat(mCarUserManagerHelper.isCurrentProcessAdminUser()).isFalse();
+ }
+
+ @Test
+ public void testAssignAdminPrivileges() {
+ int userId = 30;
+ UserInfo testInfo = createUserInfoForId(userId);
+
+ // Test that non-admins cannot assign admin privileges.
+ doReturn(false).when(mUserManager).isAdminUser(); // Current user non-admin.
+ mCarUserManagerHelper.assignAdminPrivileges(testInfo);
+ verify(mUserManager, never()).setUserAdmin(userId);
+
+ // Admins can assign admin privileges.
+ doReturn(true).when(mUserManager).isAdminUser();
+ mCarUserManagerHelper.assignAdminPrivileges(testInfo);
+ verify(mUserManager).setUserAdmin(userId);
+ }
+
+ @Test
+ public void testSetUserRestriction() {
+ int userId = 20;
+ UserInfo testInfo = createUserInfoForId(userId);
+
+ mCarUserManagerHelper.setUserRestriction(
+ testInfo, UserManager.DISALLOW_ADD_USER, /* enable= */ true);
+ verify(mUserManager).setUserRestriction(
+ UserManager.DISALLOW_ADD_USER, true, UserHandle.of(userId));
+
+ mCarUserManagerHelper.setUserRestriction(
+ testInfo, UserManager.DISALLOW_REMOVE_USER, /* enable= */ false);
+ verify(mUserManager).setUserRestriction(
+ UserManager.DISALLOW_REMOVE_USER, false, UserHandle.of(userId));
+ }
+
+ @Test
+ public void testDefaultNonAdminRestrictions() {
+ String testUserName = "Test User";
+ int userId = 20;
+ UserInfo newNonAdmin = createUserInfoForId(userId);
+
+ doReturn(newNonAdmin).when(mUserManager).createUser(testUserName, /* flags= */ 0);
+
+ mCarUserManagerHelper.createNewNonAdminUser(testUserName);
+
+ verify(mUserManager).setUserRestriction(
+ UserManager.DISALLOW_FACTORY_RESET, /* enable= */ true, UserHandle.of(userId));
+ verify(mUserManager).setUserRestriction(
+ UserManager.DISALLOW_SMS, /* enable= */ false, UserHandle.of(userId));
+ verify(mUserManager).setUserRestriction(
+ UserManager.DISALLOW_OUTGOING_CALLS, /* enable= */ false, UserHandle.of(userId));
+ }
+
+ @Test
+ public void testDefaultGuestRestrictions() {
+ int guestRestrictionsExpectedCount = 7;
+
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ mCarUserManagerHelper.initDefaultGuestRestrictions();
+
+ verify(mUserManager).setDefaultGuestRestrictions(bundleCaptor.capture());
+ Bundle guestRestrictions = bundleCaptor.getValue();
+
+ assertThat(guestRestrictions.keySet()).hasSize(guestRestrictionsExpectedCount);
+ assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_FACTORY_RESET)).isTrue();
+ assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_REMOVE_USER)).isTrue();
+ assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS)).isTrue();
+ assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)).isTrue();
+ assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_SMS)).isTrue();
+ assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_INSTALL_APPS)).isTrue();
+ assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS)).isTrue();
+ }
+
+ @Test
+ public void testAssigningAdminPrivilegesRemovesNonAdminRestrictions() {
+ int testUserId = 30;
+ boolean restrictionEnabled = false;
+ UserInfo testInfo = createUserInfoForId(testUserId);
+
+ // Only admins can assign privileges.
+ doReturn(true).when(mUserManager).isAdminUser();
+
+ mCarUserManagerHelper.assignAdminPrivileges(testInfo);
+
+ verify(mUserManager).setUserRestriction(
+ UserManager.DISALLOW_FACTORY_RESET, restrictionEnabled, UserHandle.of(testUserId));
+ }
+
+ @Test
public void testRegisterUserChangeReceiver() {
- mHelper.registerOnUsersUpdateListener(mTestListener);
+ mCarUserManagerHelper.registerOnUsersUpdateListener(mTestListener);
ArgumentCaptor<BroadcastReceiver> receiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
@@ -375,13 +641,148 @@ public class CarUserManagerHelperTest {
assertThat(handlerCaptor.getValue()).isNull();
// Unregister the receiver.
- mHelper.unregisterOnUsersUpdateListener();
+ mCarUserManagerHelper.unregisterOnUsersUpdateListener(mTestListener);
verify(mContext).unregisterReceiver(receiverCaptor.getValue());
}
+ @Test
+ public void testMultipleRegistrationsOfSameListener() {
+ CarUserManagerHelper.OnUsersUpdateListener listener =
+ Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
+
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ mCarUserManagerHelper.registerOnUsersUpdateListener(listener);
+ mCarUserManagerHelper.registerOnUsersUpdateListener(listener);
+ // Even for multiple registrations of the same listener, broadcast receiver registered once.
+ verify(mContext, times(1))
+ .registerReceiverAsUser(receiverCaptor.capture(), any(), any(), any(), any());
+
+ // Verify that calling the receiver calls the listener.
+ receiverCaptor.getValue().onReceive(mContext, new Intent());
+ verify(listener).onUsersUpdate();
+
+ // Verify that a single removal unregisters the listener.
+ mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener);
+ verify(mContext).unregisterReceiver(any());
+ }
+
+ @Test
+ public void testMultipleUnregistrationsOfTheSameListener() {
+ CarUserManagerHelper.OnUsersUpdateListener listener =
+ Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
+ mCarUserManagerHelper.registerOnUsersUpdateListener(listener);
+
+ // Verify that a multiple unregistrations cause only one unregister for broadcast receiver.
+ mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener);
+ mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener);
+ mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener);
+ verify(mContext, times(1)).unregisterReceiver(any());
+ }
+
+ @Test
+ public void testUnregisterReceiverCalledAfterAllListenersUnregister() {
+ CarUserManagerHelper.OnUsersUpdateListener listener1 =
+ Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
+ CarUserManagerHelper.OnUsersUpdateListener listener2 =
+ Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
+
+ mCarUserManagerHelper.registerOnUsersUpdateListener(listener1);
+ mCarUserManagerHelper.registerOnUsersUpdateListener(listener2);
+
+ mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener1);
+ verify(mContext, never()).unregisterReceiver(any());
+
+ mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener2);
+ verify(mContext, times(1)).unregisterReceiver(any());
+ }
+
+ @Test
+ public void testRegisteringMultipleListeners() {
+ CarUserManagerHelper.OnUsersUpdateListener listener1 =
+ Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
+ CarUserManagerHelper.OnUsersUpdateListener listener2 =
+ Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ mCarUserManagerHelper.registerOnUsersUpdateListener(listener1);
+ mCarUserManagerHelper.registerOnUsersUpdateListener(listener2);
+ verify(mContext, times(1))
+ .registerReceiverAsUser(receiverCaptor.capture(), any(), any(), any(), any());
+
+ // Verify that calling the receiver calls both listeners.
+ receiverCaptor.getValue().onReceive(mContext, new Intent());
+ verify(listener1).onUsersUpdate();
+ verify(listener2).onUsersUpdate();
+ }
+
+ @Test
+ public void testUnregisteringListenerStopsUpdatesForListener() {
+ CarUserManagerHelper.OnUsersUpdateListener listener1 =
+ Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
+ CarUserManagerHelper.OnUsersUpdateListener listener2 =
+ Mockito.mock(CarUserManagerHelper.OnUsersUpdateListener.class);
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ mCarUserManagerHelper.registerOnUsersUpdateListener(listener1);
+ mCarUserManagerHelper.registerOnUsersUpdateListener(listener2);
+ verify(mContext, times(1))
+ .registerReceiverAsUser(receiverCaptor.capture(), any(), any(), any(), any());
+
+ // Unregister listener2
+ mCarUserManagerHelper.unregisterOnUsersUpdateListener(listener2);
+
+ // Verify that calling the receiver calls only one listener.
+ receiverCaptor.getValue().onReceive(mContext, new Intent());
+ verify(listener1).onUsersUpdate();
+ verify(listener2, never()).onUsersUpdate();
+ }
+
+ @Test
+ public void testGetInitialUserWithValidLastActiveUser() {
+ SystemProperties.set("android.car.systemuser.headless", "true");
+ int lastActiveUserId = 12;
+
+ UserInfo otherUser1 = createUserInfoForId(lastActiveUserId - 2);
+ UserInfo otherUser2 = createUserInfoForId(lastActiveUserId - 1);
+ UserInfo otherUser3 = createUserInfoForId(lastActiveUserId);
+
+ mCarUserManagerHelper.setLastActiveUser(
+ lastActiveUserId, /* skipGlobalSettings= */ true);
+ mockGetUsers(mSystemUser, otherUser1, otherUser2, otherUser3);
+
+ assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId);
+ }
+
+ @Test
+ public void testGetInitialUserWithNonExistLastActiveUser() {
+ SystemProperties.set("android.car.systemuser.headless", "true");
+ int lastActiveUserId = 12;
+
+ UserInfo otherUser1 = createUserInfoForId(lastActiveUserId - 2);
+ UserInfo otherUser2 = createUserInfoForId(lastActiveUserId - 1);
+
+ mCarUserManagerHelper.setLastActiveUser(
+ lastActiveUserId, /* skipGlobalSettings= */ true);
+ mockGetUsers(mSystemUser, otherUser1, otherUser2);
+
+ assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId - 2);
+ }
+
private UserInfo createUserInfoForId(int id) {
UserInfo userInfo = new UserInfo();
userInfo.id = id;
return userInfo;
}
+
+ private void mockGetUsers(UserInfo... users) {
+ List<UserInfo> testUsers = new ArrayList<>();
+ for (UserInfo user: users) {
+ testUsers.add(user);
+ }
+ doReturn(testUsers).when(mUserManager).getUsers(true);
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index faa7bd0965..48d447bc0f 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -21,18 +21,17 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.car.user.CarUserManagerHelper;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
+import android.location.LocationManager;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.support.test.runner.AndroidJUnit4;
-import java.util.ArrayList;
-import java.util.List;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,6 +39,9 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* This class contains unit tests for the {@link CarUserService}.
*
@@ -60,17 +62,23 @@ public class CarUserServiceTest {
private Context mApplicationContext;
@Mock
+ private LocationManager mLocationManager;
+
+ @Mock
private CarUserManagerHelper mCarUserManagerHelper;
/**
* Initialize all of the objects with the @Mock annotation.
*/
@Before
- public void setUp() throws Exception {
+ public void setUpMocks() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mMockContext.getApplicationContext()).thenReturn(mApplicationContext);
+ doReturn(mApplicationContext).when(mMockContext).getApplicationContext();
+ doReturn(mLocationManager).when(mMockContext).getSystemService(Context.LOCATION_SERVICE);
mCarUserService = new CarUserService(mMockContext, mCarUserManagerHelper);
+
+ doReturn(new ArrayList<>()).when(mCarUserManagerHelper).getAllUsers();
}
/**
@@ -83,9 +91,10 @@ public class CarUserServiceTest {
mCarUserService.init();
verify(mMockContext).registerReceiver(eq(mCarUserService), argument.capture());
IntentFilter intentFilter = argument.getValue();
- assertThat(intentFilter.countActions()).isEqualTo(1);
+ assertThat(intentFilter.countActions()).isEqualTo(2);
assertThat(intentFilter.getAction(0)).isEqualTo(Intent.ACTION_LOCKED_BOOT_COMPLETED);
+ assertThat(intentFilter.getAction(1)).isEqualTo(Intent.ACTION_USER_SWITCHED);
}
/**
@@ -102,20 +111,123 @@ public class CarUserServiceTest {
*/
@Test
public void testStartsSecondaryAdminUserOnFirstRun() {
+ UserInfo admin = mockAdmin(/* adminId= */ 10);
+
+ mCarUserService.onReceive(mMockContext,
+ new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+ verify(mCarUserManagerHelper).createNewAdminUser(CarUserService.OWNER_NAME);
+ verify(mCarUserManagerHelper).switchToUser(admin);
+ }
+
+ /**
+ * Test that the {@link CarUserService} disable modify account for user 0 upon first run.
+ */
+ @Test
+ public void testDisableModifyAccountsForSystemUserOnFirstRun() {
+ // Mock system user.
+ UserInfo systemUser = new UserInfo();
+ systemUser.id = UserHandle.USER_SYSTEM;
+ doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
+
+ mockAdmin(10);
+
+ mCarUserService.onReceive(mMockContext,
+ new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+ verify(mCarUserManagerHelper)
+ .setUserRestriction(systemUser, UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+ }
+
+ /**
+ * Test that the {@link CarUserService} disable location service for user 0 upon first run.
+ */
+ @Test
+ public void testDisableLocationForSystemUserOnFirstRun() {
+ mockAdmin(/* adminId= */ 10);
+
+ mCarUserService.onReceive(mMockContext,
+ new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+ verify(mLocationManager).setLocationEnabledForUser(
+ /* enabled= */ false, UserHandle.of(UserHandle.USER_SYSTEM));
+ }
+
+ /**
+ * Test that the {@link CarUserService} updates last active user to the first admin user
+ * on first run.
+ */
+ @Test
+ public void testUpdateLastActiveUserOnFirstRun() {
+ UserInfo admin = mockAdmin(/* adminId= */ 10);
+
+ mCarUserService.onReceive(mMockContext,
+ new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+ verify(mCarUserManagerHelper)
+ .setLastActiveUser(admin.id, /* skipGlobalSetting= */ false);
+ }
+
+ /**
+ * Test that the {@link CarUserService} starts up the last active user on reboot.
+ */
+ @Test
+ public void testStartsLastActiveUserOnReboot() {
List<UserInfo> users = new ArrayList<>();
int adminUserId = 10;
UserInfo admin = new UserInfo(adminUserId, CarUserService.OWNER_NAME, UserInfo.FLAG_ADMIN);
+ int secUserId = 11;
+ UserInfo secUser =
+ new UserInfo(secUserId, CarUserService.OWNER_NAME, UserInfo.FLAG_ADMIN);
+
+ users.add(admin);
+ users.add(secUser);
+
doReturn(users).when(mCarUserManagerHelper).getAllUsers();
- // doReturn(users).when(mCarUserManagerHelper.getAllUsers());
- doReturn(admin).when(mCarUserManagerHelper).createNewAdminUser(CarUserService.OWNER_NAME);
- doReturn(true).when(mCarUserManagerHelper).switchToUser(admin);
+ doReturn(secUserId).when(mCarUserManagerHelper).getInitialUser();
mCarUserService.onReceive(mMockContext,
new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
- verify(mCarUserManagerHelper).createNewAdminUser(CarUserService.OWNER_NAME);
- verify(mCarUserManagerHelper).switchToUser(admin);
+ verify(mCarUserManagerHelper).switchToUserId(secUserId);
+ }
+
+ /**
+ * Test that the {@link CarUserService} updates last active user on user switch intent.
+ */
+ @Test
+ public void testLastActiveUserUpdatedOnUserSwitch() {
+ int lastActiveUserId = 11;
+
+ Intent intent = new Intent(Intent.ACTION_USER_SWITCHED);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, lastActiveUserId);
+
+ doReturn(true).when(mCarUserManagerHelper).isPersistentUser(lastActiveUserId);
+
+ mCarUserService.onReceive(mMockContext, intent);
+
+ verify(mCarUserManagerHelper).setLastActiveUser(
+ lastActiveUserId, /* skipGlobalSetting= */ false);
+ }
+
+ /**
+ * Test that the {@link CarUserService} sets default guest restrictions on first boot.
+ */
+ @Test
+ public void testInitializeGuestRestrictionsOnFirstRun() {
+ mockAdmin(/* adminId= */ 10);
+
+ mCarUserService.onReceive(mMockContext,
+ new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+ verify(mCarUserManagerHelper).initDefaultGuestRestrictions();
+ }
+
+ private UserInfo mockAdmin(int adminId) {
+ UserInfo admin = new UserInfo(adminId, CarUserService.OWNER_NAME, UserInfo.FLAG_ADMIN);
+ doReturn(admin).when(mCarUserManagerHelper).createNewAdminUser(CarUserService.OWNER_NAME);
+ return admin;
}
}
diff --git a/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java b/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java
index 9c394459ea..fcfd6dc056 100644
--- a/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java
+++ b/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java
@@ -122,7 +122,7 @@ public class CarUserManagerHelperRoboTest {
}
@Test
- public void testGetAllUsersExcludesForegroundUser() {
+ public void testGetAllUsersExceptForegroundUser() {
ShadowActivityManager.setCurrentUser(11);
ShadowUserManager userManager = ShadowUserManager.getShadow();