Merge "New Manager for Trusted device enrollment."
diff --git a/EncryptionRunner/Android.bp b/EncryptionRunner/Android.bp
new file mode 100644
index 0000000..b02e6de
--- /dev/null
+++ b/EncryptionRunner/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_library {
+ name: "EncryptionRunner",
+ min_sdk_version: "23",
+ product_variables: {
+ pdk: {
+ enabled: false,
+ },
+ },
+ srcs: [
+ "src/**/*.java",
+ ],
+}
+
+android_test {
+ name: "EncryptionRunnerTest",
+ min_sdk_version: "23",
+ srcs: [
+ "test/**/*.java",
+ ],
+ product_variables: {
+ pdk: {
+ enabled: false,
+ },
+ },
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "EncryptionRunner",
+ "junit",
+ "truth-prebuilt",
+ ],
+}
diff --git a/EncryptionRunner/AndroidManifest.xml b/EncryptionRunner/AndroidManifest.xml
new file mode 100644
index 0000000..8d7643e
--- /dev/null
+++ b/EncryptionRunner/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="android.car.encryptionrunner" >
+ <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.car.encryptionrunner"
+ android:label="Encryption Runner Tests" />
+</manifest>
diff --git a/EncryptionRunner/AndroidTest.xml b/EncryptionRunner/AndroidTest.xml
new file mode 100644
index 0000000..b6cbbfa
--- /dev/null
+++ b/EncryptionRunner/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Tests for EncryptionRunner.">
+ <option name="test-tag" value="EncryptionRunnerTest" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="EncryptionRunnerTest.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.car.encryptionrunner" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java b/EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
new file mode 100644
index 0000000..b08c985
--- /dev/null
+++ b/EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.encryptionrunner;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An ecnryption runnner that doesn't actually do encryption. Useful for debugging. Do not use in
+ * production environments.
+ */
+class DummyEncryptionRunner implements EncryptionRunner {
+
+ private static final String KEY = "key";
+ private static final String INIT = "init";
+ private static final String INIT_RESPONSE = "initResponse";
+ private static final String CLIENT_RESPONSE = "clientResponse";
+ public static final String PIN = "1234";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({Mode.UNKNOWN, Mode.CLIENT, Mode.SERVER})
+ private @interface Mode {
+
+ int UNKNOWN = 0;
+ int CLIENT = 1;
+ int SERVER = 2;
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({State.UNKNOWN, State.WAITING_FOR_RESPONSE, State.FINISHED})
+ private @interface State {
+
+ int UNKNOWN = 0;
+ int WAITING_FOR_RESPONSE = 1;
+ int FINISHED = 2;
+ }
+
+ @Mode
+ private int mMode;
+ @State
+ private int mState;
+
+ @Override
+ public HandshakeMessage initHandshake() {
+ mMode = Mode.CLIENT;
+ mState = State.WAITING_FOR_RESPONSE;
+ return HandshakeMessage.newBuilder()
+ .setNextMessage(INIT.getBytes())
+ .build();
+ }
+
+ @Override
+ public HandshakeMessage respondToInitRequest(byte[] initializationRequest)
+ throws HandshakeException {
+ mMode = Mode.SERVER;
+ if (!new String(initializationRequest).equals(INIT)) {
+ throw new HandshakeException("Unexpected initialization request");
+ }
+ mState = State.WAITING_FOR_RESPONSE;
+ return HandshakeMessage.newBuilder()
+ .setNextMessage(INIT_RESPONSE.getBytes())
+ .build();
+ }
+
+ @Override
+ public HandshakeMessage continueHandshake(byte[] response) throws HandshakeException {
+ if (mState != State.WAITING_FOR_RESPONSE) {
+ throw new HandshakeException("not waiting for response but got one");
+ }
+ switch(mMode) {
+ case Mode.SERVER:
+ if (!CLIENT_RESPONSE.equals(new String(response))) {
+ throw new HandshakeException("unexpected response: " + new String(response));
+ }
+ mState = State.FINISHED;
+ return HandshakeMessage.newBuilder()
+ .setHandshakeComplete(true)
+ .setKey(new DummyKey())
+ .build();
+ case Mode.CLIENT:
+ if (!INIT_RESPONSE.equals(new String(response))) {
+ throw new HandshakeException("unexpected response: " + new String(response));
+ }
+ mState = State.FINISHED;
+ return HandshakeMessage.newBuilder()
+ .setHandshakeComplete(true)
+ .setKey(new DummyKey())
+ .setNextMessage(CLIENT_RESPONSE.getBytes())
+ .build();
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public Key keyOf(byte[] serialized) {
+ return new DummyKey();
+ }
+
+ @Override
+ public String getPin() {
+ return PIN;
+ }
+
+ @Override
+ public byte[] encryptData(Key key, byte[] data) {
+ return data;
+ }
+
+ @Override
+ public byte[] decryptData(Key key, byte[] encryptedData) {
+ return encryptedData;
+ }
+
+ private class DummyKey implements Key {
+
+ @Override
+ public byte[] asBytes() {
+ return KEY.getBytes();
+ }
+ }
+}
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java b/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java
new file mode 100644
index 0000000..7e7bd3e
--- /dev/null
+++ b/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.encryptionrunner;
+
+import android.annotation.NonNull;
+
+/**
+ * A generalized interface that allows for generating shared secrets as well as encrypting
+ * messages.
+ */
+public interface EncryptionRunner {
+
+ /**
+ * Starts an encryption handshake.
+ *
+ * @return A handshake message with information about the handshake that is started.
+ */
+ HandshakeMessage initHandshake();
+
+ /**
+ * Starts an encryption handshake where the device that is being communicated with already
+ * initiated the request.
+ *
+ * @param initializationRequest the bytes that the other device sent over.
+ * @return a handshake message with information about the handshake.
+ * @throws HandshakeException if initialization request is invalid.
+ */
+ HandshakeMessage respondToInitRequest(@NonNull byte[] initializationRequest)
+ throws HandshakeException;
+
+ /**
+ * Continues a handshake after receiving another response from the connected device.
+ *
+ * @param response the response from the other device.
+ * @return a message that can be used to continue the handshake.
+ * @throws HandshakeException if unexpected bytes in response.
+ */
+ HandshakeMessage continueHandshake(@NonNull byte[] response) throws HandshakeException;
+
+ /**
+ * De seriliazes a previously serilized key generated by an instance of this encryption runner.
+ *
+ * @param serialized the serialized bytes of the key.
+ * @return the Key object used for encryption.
+ */
+ Key keyOf(@NonNull byte[] serialized);
+
+ /**
+ * A user visible shared pin. This pin can be used to verify that both devices that are
+ * communicating have agreed to the same key and will be shown to a user.
+ *
+ * @return the user visible pin.
+ */
+ String getPin();
+
+ /**
+ * Encrypts data using an encryption key.
+ *
+ * @param key the key used to encrypt the data.
+ * @param data the data to be encrypted
+ * @return the encrypted data.
+ */
+ byte[] encryptData(@NonNull Key key, @NonNull byte[] data);
+
+ /**
+ * Decrypts data using a specified key.
+ *
+ * @param key The key used to decrypt the data.
+ * @param encryptedData The encrypted data.
+ * @return decrypted data.
+ */
+ byte[] decryptData(@NonNull Key key, @NonNull byte[] encryptedData);
+}
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java b/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java
new file mode 100644
index 0000000..d975835
--- /dev/null
+++ b/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.encryptionrunner;
+
+/**
+ * Factory that creates encryption runner.
+ */
+public class EncryptionRunnerFactory {
+
+ /**
+ * Creates a new {@link EncryptionRunner} one that doesn't actually do encryption but is useful
+ * for testing.
+ */
+ static EncryptionRunner newDummyRunner() {
+ return new DummyEncryptionRunner();
+ }
+}
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/HandshakeException.java b/EncryptionRunner/src/android/car/encryptionrunner/HandshakeException.java
new file mode 100644
index 0000000..02c873c
--- /dev/null
+++ b/EncryptionRunner/src/android/car/encryptionrunner/HandshakeException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.encryptionrunner;
+
+/**
+ * Exception indicating an error during a Handshake of EncryptionRunner.
+ */
+public class HandshakeException extends Exception {
+
+ public HandshakeException(String message) {
+ super(message);
+ }
+}
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java b/EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java
new file mode 100644
index 0000000..5286770
--- /dev/null
+++ b/EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.encryptionrunner;
+
+import android.annotation.Nullable;
+
+/**
+ * During an {@link EncryptionRunner} handshake process, these are the messages returned as part
+ * of each step.
+ */
+public class HandshakeMessage {
+
+ private final boolean mHandShakeComplete;
+ private final Key mKey;
+ private final byte[] mNextMessage;
+
+ /**
+ * @return Returns a builder for {@link HandshakeMessage}.
+ */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * Use the builder;
+ */
+ private HandshakeMessage(
+ boolean handShakeComplete,
+ @Nullable Key key,
+ @Nullable byte[] nextMessage) {
+ mHandShakeComplete = handShakeComplete;
+ mKey = key;
+ mNextMessage = nextMessage;
+ }
+
+ /**
+ * Returns the next message to send in a handshake.
+ */
+ @Nullable
+ public byte[] getNextMessage() {
+ return mNextMessage == null ? null : mNextMessage.clone();
+ }
+
+ /**
+ * Returns true if the handshake is complete.
+ */
+ public boolean isHandShakeComplete() {
+ return mHandShakeComplete;
+ }
+
+ /**
+ * Returns the encryption key that can be used to encrypt data.
+ */
+ @Nullable
+ public Key getKey() {
+ return mKey;
+ }
+
+ static class Builder {
+ boolean mHandshakeComplete;
+ Key mKey;
+ byte[] mNextMessage;
+
+ Builder setHandshakeComplete(boolean handshakeComplete) {
+ mHandshakeComplete = handshakeComplete;
+ return this;
+ }
+
+ Builder setKey(Key key) {
+ mKey = key;
+ return this;
+ }
+
+ Builder setNextMessage(byte[] nextMessage) {
+ mNextMessage = nextMessage == null ? null : nextMessage.clone();
+ return this;
+ }
+
+ HandshakeMessage build() {
+ return new HandshakeMessage(mHandshakeComplete, mKey, mNextMessage);
+ }
+ }
+}
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/Key.java b/EncryptionRunner/src/android/car/encryptionrunner/Key.java
new file mode 100644
index 0000000..97dd362
--- /dev/null
+++ b/EncryptionRunner/src/android/car/encryptionrunner/Key.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.encryptionrunner;
+
+import android.annotation.NonNull;
+
+/**
+ * Represents a serializable encryption key.
+ */
+public interface Key {
+ /**
+ * Returns a serialized encryption key.
+ */
+ @NonNull byte[] asBytes();
+}
diff --git a/EncryptionRunner/test/android/car/encryptionrunner/EncryptionRunnerTest.java b/EncryptionRunner/test/android/car/encryptionrunner/EncryptionRunnerTest.java
new file mode 100644
index 0000000..d08b37a
--- /dev/null
+++ b/EncryptionRunner/test/android/car/encryptionrunner/EncryptionRunnerTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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 android.car.encryptionrunner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class EncryptionRunnerTest {
+
+ private static final byte[] sTestData = "test data".getBytes();
+
+ @Test
+ public void happyFlow() throws Exception {
+ // This performs a handshake and then sends an "encrypted" message back and forth.
+ // Any encryption runner should be able to do this.
+ // Right now just using the dummy runner, when we have a real runner we can extract this
+ // method or just have the factory create a real runner.
+ EncryptionRunner clientRunner = EncryptionRunnerFactory.newDummyRunner();
+ EncryptionRunner serverRunner = EncryptionRunnerFactory.newDummyRunner();
+ HandshakeMessage initialClientMessage = clientRunner.initHandshake();
+
+ assertThat(initialClientMessage.isHandShakeComplete()).isFalse();
+ assertThat(initialClientMessage.getKey()).isNull();
+ assertThat(initialClientMessage.getNextMessage()).isNotNull();
+
+ HandshakeMessage initialServerMessage =
+ serverRunner.respondToInitRequest(initialClientMessage.getNextMessage());
+
+ assertThat(initialServerMessage.isHandShakeComplete()).isFalse();
+ assertThat(initialServerMessage.getKey()).isNull();
+ assertThat(initialServerMessage.getNextMessage()).isNotNull();
+
+ HandshakeMessage clientMessage =
+ clientRunner.continueHandshake(initialServerMessage.getNextMessage());
+
+ assertThat(clientMessage.isHandShakeComplete()).isTrue();
+ assertThat(clientMessage.getKey()).isNotNull();
+ assertThat(clientMessage.getNextMessage()).isNotNull();
+
+ HandshakeMessage serverMessage =
+ serverRunner.continueHandshake(clientMessage.getNextMessage());
+
+ assertThat(serverMessage.isHandShakeComplete()).isTrue();
+ assertThat(serverMessage.getKey()).isNotNull();
+ assertThat(serverMessage.getNextMessage()).isNull();
+
+ assertThat(serverRunner.decryptData(
+ serverMessage.getKey(),
+ clientRunner.encryptData(clientMessage.getKey(), sTestData))).isEqualTo(sTestData);
+ assertThat(clientRunner.decryptData(
+ clientMessage.getKey(),
+ serverRunner.encryptData(serverMessage.getKey(), sTestData))).isEqualTo(sTestData);
+ }
+
+}
diff --git a/car-lib/api/test-current.txt b/car-lib/api/test-current.txt
index ca87f9a..02cbd6c 100644
--- a/car-lib/api/test-current.txt
+++ b/car-lib/api/test-current.txt
@@ -6,6 +6,15 @@
}
+package android.car.drivingstate {
+
+ public final class CarUxRestrictionsManager {
+ method public synchronized android.car.drivingstate.CarUxRestrictionsConfiguration getConfig() throws android.car.CarNotConnectedException;
+ method public synchronized android.car.drivingstate.CarUxRestrictionsConfiguration getStagedConfig() throws android.car.CarNotConnectedException;
+ }
+
+}
+
package android.car.media {
public final class CarAudioManager {
diff --git a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
index b93b9e3..7b60538 100644
--- a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
+++ b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
import android.car.Car;
import android.car.CarManagerBase;
import android.car.CarNotConnectedException;
@@ -178,6 +179,49 @@
}
/**
+ * Get the current staged configuration, staged config file will only be accessible after
+ * the boot up completed or user has been switched.
+ * This methods is only for test purpose, please do not use in production.
+ *
+ * @return current staged configuration, {@code null} if it's not available
+ *
+ * @hide
+ *
+ */
+ @TestApi
+ @Nullable
+ @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
+ public synchronized CarUxRestrictionsConfiguration getStagedConfig()
+ throws CarNotConnectedException {
+ try {
+ return mUxRService.getStagedConfig();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not get staged UX restrictions staged configuration " + e);
+ throw new CarNotConnectedException(e);
+ }
+ }
+
+ /**
+ * Get the current prod configuration
+ *
+ * @return current prod configuration that is in effect.
+ *
+ * @hide
+ *
+ */
+ @TestApi
+ @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
+ public synchronized CarUxRestrictionsConfiguration getConfig()
+ throws CarNotConnectedException {
+ try {
+ return mUxRService.getConfig();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not get production UX restrictions prod configuration" + e);
+ throw new CarNotConnectedException(e);
+ }
+ }
+
+ /**
* Class that implements the listener interface and gets called back from the
* {@link com.android.car.CarDrivingStateService} across the binder interface.
*/
diff --git a/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl b/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
index 270c74e..8f48c5e 100644
--- a/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
+++ b/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
@@ -32,4 +32,6 @@
void unregisterUxRestrictionsChangeListener(in ICarUxRestrictionsChangeListener listener) = 1;
CarUxRestrictions getCurrentUxRestrictions() = 2;
boolean saveUxRestrictionsConfigurationForNextBoot(in CarUxRestrictionsConfiguration config) = 3;
+ CarUxRestrictionsConfiguration getStagedConfig() = 4;
+ CarUxRestrictionsConfiguration getConfig() = 5;
}
diff --git a/service/src/com/android/car/CarUxRestrictionsManagerService.java b/service/src/com/android/car/CarUxRestrictionsManagerService.java
index 868b825..d4d34be 100644
--- a/service/src/com/android/car/CarUxRestrictionsManagerService.java
+++ b/service/src/com/android/car/CarUxRestrictionsManagerService.java
@@ -194,9 +194,8 @@
mContext.registerReceiver(mBroadcastReceiver, filter);
}
- @VisibleForTesting
- @Nullable
- /* package */ CarUxRestrictionsConfiguration getConfig() {
+ @Override
+ public CarUxRestrictionsConfiguration getConfig() {
return mCarUxRestrictionsConfiguration;
}
@@ -398,6 +397,18 @@
return persistConfig(config, CONFIG_FILENAME_STAGED);
}
+ @Override
+ @Nullable
+ public CarUxRestrictionsConfiguration getStagedConfig() {
+ File stagedConfig = mContext.getFileStreamPath(CONFIG_FILENAME_STAGED);
+ if (stagedConfig.exists()) {
+ logd("Attempting to read staged config");
+ return readPersistedConfig(stagedConfig);
+ } else {
+ return null;
+ }
+ }
+
/**
* Writes configuration into the specified file.
*
@@ -668,13 +679,13 @@
CarUxRestrictionsConfiguration createDefaultConfig() {
return new CarUxRestrictionsConfiguration.Builder()
.setUxRestrictions(CarDrivingStateEvent.DRIVING_STATE_PARKED,
- false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE)
+ false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE)
.setUxRestrictions(CarDrivingStateEvent.DRIVING_STATE_IDLING,
- false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE)
+ false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE)
.setUxRestrictions(CarDrivingStateEvent.DRIVING_STATE_MOVING,
- true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED)
+ true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED)
.setUxRestrictions(CarDrivingStateEvent.DRIVING_STATE_UNKNOWN,
- true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED)
+ true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED)
.build();
}
diff --git a/tests/UxRestrictionsSample/res/layout/main_activity.xml b/tests/UxRestrictionsSample/res/layout/main_activity.xml
index 851497a..0b097fb 100644
--- a/tests/UxRestrictionsSample/res/layout/main_activity.xml
+++ b/tests/UxRestrictionsSample/res/layout/main_activity.xml
@@ -26,41 +26,50 @@
android:orientation="vertical">
<TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:text="@string/status_header"
android:layout_gravity="center"
android:padding="@dimen/section_padding"
android:textSize="@dimen/header_text_size"
- android:layout_width="match_parent"
- android:textAppearance="?android:textAppearanceLarge"
- android:layout_height="wrap_content"/>
+ android:textAppearance="?android:textAppearanceLarge"/>
<TextView
android:id="@+id/driving_state"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:text="@string/driving_state"
android:textSize="@dimen/info_text_size"
android:layout_gravity="center"
android:padding="@dimen/section_padding"
- android:layout_width="match_parent"
- android:textAppearance="?android:textAppearanceLarge"
- android:layout_height="wrap_content"/>
+ android:textAppearance="?android:textAppearanceLarge"/>
<TextView
android:id="@+id/do_status"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:text="@string/is_do_reqd"
android:textSize="@dimen/info_text_size"
android:padding="@dimen/section_padding"
- android:layout_width="match_parent"
- android:textAppearance="?android:textAppearanceLarge"
- android:layout_height="wrap_content"/>
+ android:textAppearance="?android:textAppearanceLarge"/>
<TextView
android:id="@+id/uxr_status"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:text="@string/active_restrictions"
android:padding="@dimen/section_padding"
android:textSize="@dimen/info_text_size"
+ android:textAppearance="?android:textAppearanceLarge"/>
+
+ <TextView
+ android:id="@+id/show_uxr_config"
android:layout_width="match_parent"
- android:textAppearance="?android:textAppearanceLarge"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:text="@string/uxr_config_header"
+ android:padding="@dimen/section_padding"
+ android:textSize="@dimen/info_text_size"
+ android:textAppearance="?android:textAppearanceLarge"/>
<View
android:layout_width="match_parent"
@@ -70,12 +79,12 @@
android:background="@android:color/darker_gray"/>
<TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:text="@string/action_header"
android:padding="@dimen/section_padding"
android:textSize="@dimen/header_text_size"
- android:layout_width="match_parent"
- android:textAppearance="?android:textAppearanceLarge"
- android:layout_height="wrap_content"/>
+ android:textAppearance="?android:textAppearanceLarge"/>
<LinearLayout
android:layout_width="match_parent"
@@ -84,10 +93,29 @@
android:id="@+id/toggle_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/section_padding"
android:padding="@dimen/section_padding"
android:text="@string/disable_uxr"
android:textAllCaps="false"
android:textSize="@dimen/info_text_size"/>
+ <Button
+ android:id="@+id/show_staged_config"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/section_padding"
+ android:padding="@dimen/section_padding"
+ android:text="@string/show_staged_config"
+ android:textAllCaps="false"
+ android:textSize="@dimen/info_text_size"/>
+ <Button
+ android:id="@+id/show_prod_config"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/section_padding"
+ android:padding="@dimen/section_padding"
+ android:text="@string/show_prod_config"
+ android:textAllCaps="false"
+ android:textSize="@dimen/info_text_size"/>
</LinearLayout>
<View
@@ -98,12 +126,12 @@
android:background="@android:color/darker_gray"/>
<TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:text="@string/sample_header"
android:padding="@dimen/section_padding"
android:textSize="@dimen/header_text_size"
- android:layout_width="match_parent"
- android:textAppearance="?android:textAppearanceLarge"
- android:layout_height="wrap_content"/>
+ android:textAppearance="?android:textAppearanceLarge"/>
<LinearLayout
android:layout_width="match_parent"
@@ -112,6 +140,7 @@
android:id="@+id/launch_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:padding="@dimen/section_padding"
android:text="@string/sample_msg_activity"
android:textAllCaps="false"
android:textSize="@dimen/info_text_size"/>
@@ -125,12 +154,12 @@
android:background="@android:color/darker_gray"/>
<TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:text="@string/save_uxr_config_header"
android:padding="@dimen/section_padding"
android:textSize="@dimen/header_text_size"
- android:layout_width="match_parent"
- android:textAppearance="?android:textAppearanceLarge"
- android:layout_height="wrap_content"/>
+ android:textAppearance="?android:textAppearanceLarge"/>
<LinearLayout
android:layout_width="match_parent"
@@ -139,6 +168,7 @@
android:id="@+id/save_uxr_config"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:padding="@dimen/section_padding"
android:text="@string/save_uxr_config"
android:textSize="@dimen/info_text_size"/>
</LinearLayout>
diff --git a/tests/UxRestrictionsSample/res/values/strings.xml b/tests/UxRestrictionsSample/res/values/strings.xml
index aa32ff4..b36d7f9 100644
--- a/tests/UxRestrictionsSample/res/values/strings.xml
+++ b/tests/UxRestrictionsSample/res/values/strings.xml
@@ -20,8 +20,11 @@
<string name="driving_state" translatable="false">Driving State: </string>
<string name="is_do_reqd" translatable="false">Distraction Optimization Required? </string>
<string name="active_restrictions" translatable="false">Active UX Restrictions: </string>
+ <string name="uxr_config_header" translatable="false">UxR Configuration: </string>
<string name="action_header" translatable="false"><u>Available Actions</u></string>
<string name="disable_uxr" translatable="false">Disable Ux Restriction Engine</string>
+ <string name="show_staged_config" translatable="false">Show Staged Config</string>
+ <string name="show_prod_config" translatable="false">Show Production Config</string>
<string name="sample_header" translatable="false"><u>Sample Activities</u></string>
<string name="sample_msg_activity" translatable="false">Sample Message Activity</string>
<string name="return_home" translatable="false"><u>Return Home</u></string>
@@ -30,4 +33,8 @@
<string name="set_uxr_config_dialog_title" translatable="false">Select restrictions for IDLING/MOVING</string>
<string name="set_uxr_config_dialog_negative_button" translatable="false">Cancel</string>
<string name="set_uxr_config_dialog_positive_button" translatable="false">Save UXR Config</string>
+ <string name="no_staged_config" translatable="false">There is no staged configuration found</string>
+ <string name="no_prod_config" translatable="false">There is no production configuration found</string>
+ <string name="staged_config_title" translatable="false">Staged Config</string>
+ <string name="prod_config_title" translatable="false">Production Config</string>
</resources>
diff --git a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
index cd9c015..27738bc 100644
--- a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
+++ b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
@@ -34,11 +34,14 @@
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.JsonWriter;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
+import java.io.CharArrayWriter;
+
/**
* Sample app that uses components in car support library to demonstrate Car drivingstate UXR
* status.
@@ -47,7 +50,7 @@
public static final String TAG = "drivingstate";
// Order of elements is based on number of bits shifted in value of the constants.
- private static final CharSequence[] UX_RESTRICTION_NAMES = new CharSequence[] {
+ private static final CharSequence[] UX_RESTRICTION_NAMES = new CharSequence[]{
"BASELINE",
"NO_DIALPAD",
"NO_FILTERING",
@@ -70,6 +73,8 @@
private Button mToggleButton;
private Button mSampleMsgButton;
private Button mSaveUxrConfigButton;
+ private Button mShowStagedConfig;
+ private Button mShowProdConfig;
private boolean mEnableUxR;
@@ -86,7 +91,6 @@
Car.CAR_UX_RESTRICTION_SERVICE);
mCarPackageManager = (CarPackageManager) mCar.getCarManager(
Car.PACKAGE_SERVICE);
-
if (mCarDrivingStateManager != null) {
mCarDrivingStateManager.registerListener(mDrvStateChangeListener);
updateDrivingStateText(
@@ -183,6 +187,10 @@
mSaveUxrConfigButton = findViewById(R.id.save_uxr_config);
mSaveUxrConfigButton.setOnClickListener(v -> saveUxrConfig());
+ mShowStagedConfig = findViewById(R.id.show_staged_config);
+ mShowStagedConfig.setOnClickListener(v -> showStagedUxRestrictionsConfig());
+ mShowProdConfig = findViewById(R.id.show_prod_config);
+ mShowProdConfig.setOnClickListener(v -> showProdUxRestrictionsConfig());
mToggleButton.setOnClickListener(v -> updateToggleUxREnable());
mSampleMsgButton = findViewById(R.id.launch_message);
@@ -228,6 +236,56 @@
}
}
+ private void showStagedUxRestrictionsConfig() {
+ try {
+ CarUxRestrictionsConfiguration stagedConfig =
+ mCarUxRestrictionsManager.getStagedConfig();
+ if (stagedConfig == null) {
+ new AlertDialog.Builder(this)
+ .setMessage(R.string.no_staged_config)
+ .show();
+ return;
+ }
+ CharArrayWriter charWriter = new CharArrayWriter();
+ JsonWriter writer = new JsonWriter(charWriter);
+ writer.setIndent("\t");
+ stagedConfig.writeJson(writer);
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.staged_config_title)
+ .setMessage(charWriter.toString())
+ .show();
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car not connected", e);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void showProdUxRestrictionsConfig() {
+ try {
+ CarUxRestrictionsConfiguration prodConfig =
+ mCarUxRestrictionsManager.getConfig();
+ if (prodConfig == null) {
+ new AlertDialog.Builder(this)
+ .setMessage(R.string.no_prod_config)
+ .show();
+ return;
+ }
+ CharArrayWriter charWriter = new CharArrayWriter();
+ JsonWriter writer = new JsonWriter(charWriter);
+ writer.setIndent("\t");
+ prodConfig.writeJson(writer);
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.prod_config_title)
+ .setMessage(charWriter.toString())
+ .show();
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car not connected", e);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
private void launchSampleMsgActivity(View view) {
Intent msgIntent = new Intent(this, SampleMessageActivity.class);
startActivity(msgIntent);