Merge "Create a bugreport service" into qt-dev
diff --git a/car-lib/src/android/car/hardware/CarPropertyConfig.java b/car-lib/src/android/car/hardware/CarPropertyConfig.java
index d513415..e271fd5 100644
--- a/car-lib/src/android/car/hardware/CarPropertyConfig.java
+++ b/car-lib/src/android/car/hardware/CarPropertyConfig.java
@@ -108,26 +108,50 @@
public static final int VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS = 2;
/**
- * @return Access type of properties.
- * None = 0, READ = 1, WRITE = 2, READ_WRITE = 3
+ * Return the access type of the car property.
+ * <p>The access type could be one of the following:
+ * <ul>
+ * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_NONE}</li>
+ * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}</li>
+ * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_WRITE}</li>
+ * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE}</li>
+ * </ul>
+ *
+ * @return the access type of the car property.
*/
public @VehiclePropertyAccessType int getAccess() {
return mAccess;
}
/**
+ * Return the area type of the car property.
+ * <p>The area type could be one of the following:
+ * <ul>
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}</li>
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_WINDOW}</li>
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_SEAT}</li>
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_DOOR}</li>
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_MIRROR}</li>
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_WHEEL}</li>
+ * </ul>
*
- * @return Area type of properties.
- * Global = 0, Window = 2, Seat = 3, Door = 4, Mirror = 5, Wheel = 6
+ * @return the area type of the car property.
*/
public @VehicleAreaTypeValue int getAreaType() {
return mAreaType;
}
/**
+ * Return the change mode of the car property.
*
- * @return Change mode of properties.
- * STATIC = 0, ON_CHANGE = 1, CONTINUOUS = 2
+ * <p>The change mode could be one of the following:
+ * <ul>
+ * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC }</li>
+ * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}</li>
+ * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}</li>
+ * </ul>
+ *
+ * @return the change mode of properties.
*/
public @VehiclePropertyChangeModeType int getChangeMode() {
return mChangeMode;
diff --git a/car-lib/src/android/car/hardware/property/CarPropertyManager.java b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
index d45ed3b..6317c66 100644
--- a/car-lib/src/android/car/hardware/property/CarPropertyManager.java
+++ b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
@@ -48,7 +48,6 @@
private static final boolean DBG = false;
private static final String TAG = "CarPropertyManager";
private static final int MSG_GENERIC_EVENT = 0;
- private final List<CarPropertyConfig> mConfigs;
private final SingleMessageHandler<CarPropertyEvent> mHandler;
private final ICarProperty mService;
@@ -57,6 +56,8 @@
/** Record of locally active properties. Key is propertyId */
private final SparseArray<CarPropertyListeners> mActivePropertyListener =
new SparseArray<>();
+ /** Record of properties' configs. Key is propertyId */
+ private final SparseArray<CarPropertyConfig> mConfigMap = new SparseArray<>();
/**
* Application registers {@link CarPropertyEventCallback} object to receive updates and changes
@@ -78,15 +79,15 @@
}
/** Read ON_CHANGE sensors */
- public static final float SENSOR_RATE_ONCHANGE = 0;
+ public static final float SENSOR_RATE_ONCHANGE = 0f;
/** Read sensors at the rate of 1 hertz */
- public static final float SENSOR_RATE_NORMAL = 1;
+ public static final float SENSOR_RATE_NORMAL = 1f;
/** Read sensors at the rate of 5 hertz */
- public static final float SENSOR_RATE_UI = 5;
+ public static final float SENSOR_RATE_UI = 5f;
/** Read sensors at the rate of 10 hertz */
- public static final float SENSOR_RATE_FAST = 10;
+ public static final float SENSOR_RATE_FAST = 10f;
/** Read sensors at the rate of 100 hertz */
- public static final float SENSOR_RATE_FASTEST = 100;
+ public static final float SENSOR_RATE_FASTEST = 100f;
/**
* Get an instance of the CarPropertyManager.
@@ -99,7 +100,10 @@
public CarPropertyManager(@NonNull ICarProperty service, @Nullable Handler handler) {
mService = service;
try {
- mConfigs = mService.getPropertyList();
+ List<CarPropertyConfig> configs = mService.getPropertyList();
+ for (CarPropertyConfig carPropertyConfig : configs) {
+ mConfigMap.put(carPropertyConfig.getPropertyId(), carPropertyConfig);
+ }
} catch (Exception e) {
Log.e(TAG, "getPropertyList exception ", e);
throw new RuntimeException(e);
@@ -135,14 +139,33 @@
/**
* Register {@link CarPropertyEventCallback} to get property updates. Multiple listeners
- * can be registered for a single sensor or the same listener can be used for different sensors.
- * If the same listener is registered again for the same sensor, it will be either ignored or
- * updated depending on the rate.
+ * can be registered for a single property or the same listener can be used for different
+ * properties. If the same listener is registered again for the same property, it will be
+ * updated to new rate.
+ * <p>Rate could be one of the following:
+ * <ul>
+ * <li>{@link CarPropertyManager#SENSOR_RATE_ONCHANGE}</li>
+ * <li>{@link CarPropertyManager#SENSOR_RATE_NORMAL}</li>
+ * <li>{@link CarPropertyManager#SENSOR_RATE_UI}</li>
+ * <li>{@link CarPropertyManager#SENSOR_RATE_FAST}</li>
+ * <li>{@link CarPropertyManager#SENSOR_RATE_FASTEST}</li>
+ * </ul>
+ * <p>
+ * <b>Note:</b>Rate has no effect if the property has one of the following change modes:
+ * <ul>
+ * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC}</li>
+ * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}</li>
+ * </ul>
+ * See {@link CarPropertyConfig#getChangeMode()} for details.
+ * If rate is higher than {@link CarPropertyConfig#getMaxSampleRate()}, it will be registered
+ * with max sample rate.
+ * If rate is lower than {@link CarPropertyConfig#getMinSampleRate()}, it will be registered
+ * with min sample rate.
*
* @param callback CarPropertyEventCallback to be registered.
* @param propertyId PropertyId to subscribe
- * @param rate rate how fast the sensor events are delivered.
- * @return if the sensor was successfully enabled.
+ * @param rate how fast the property events are delivered in Hz.
+ * @return true if the listener is successfully registered.
* @throws SecurityException if missing the appropriate permission.
*/
public boolean registerCallback(@NonNull CarPropertyEventCallback callback,
@@ -151,6 +174,14 @@
if (mCarPropertyEventToService == null) {
mCarPropertyEventToService = new CarPropertyEventListenerToService(this);
}
+ CarPropertyConfig config = mConfigMap.get(propertyId);
+ if (config == null) {
+ Log.e(TAG, "registerListener: propId is not in config list: " + propertyId);
+ return false;
+ }
+ if (config.getChangeMode() == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE) {
+ rate = SENSOR_RATE_ONCHANGE;
+ }
boolean needsServerUpdate = false;
CarPropertyListeners listeners;
listeners = mActivePropertyListener.get(propertyId);
@@ -257,7 +288,11 @@
*/
@NonNull
public List<CarPropertyConfig> getPropertyList() {
- return mConfigs;
+ List<CarPropertyConfig> configs = new ArrayList<>(mConfigMap.size());
+ for (int i = 0; i < mConfigMap.size(); i++) {
+ configs.add(mConfigMap.valueAt(i));
+ }
+ return configs;
}
/**
@@ -268,9 +303,10 @@
@NonNull
public List<CarPropertyConfig> getPropertyList(@NonNull ArraySet<Integer> propertyIds) {
List<CarPropertyConfig> configs = new ArrayList<>();
- for (CarPropertyConfig c : mConfigs) {
- if (propertyIds.contains(c.getPropertyId())) {
- configs.add(c);
+ for (int propId : propertyIds) {
+ CarPropertyConfig config = mConfigMap.get(propId);
+ if (config != null) {
+ configs.add(config);
}
}
return configs;
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 93021b5..acc7a58 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -256,7 +256,7 @@
<string name="car_permission_label_enroll_trust">Enroll Trusted Device</string>
<string name="car_permission_desc_enroll_trust">Allow Trusted Device Enrollment</string>
- <!-- The default name of device enrolled as trust device [CHAR LIMIT=16] -->
+ <!-- The default name of device enrolled as trust device [CHAR LIMIT=NONE] -->
<string name="trust_device_default_name">My Device</string>
<!-- The package name of the media application that will be selected as the default [CHAR LIMIT=NONE] -->
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index b4faa82..5377920 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -141,7 +141,10 @@
// onto disk, so it's sufficient to do it once + we minimize the number of disk writes.
if (Settings.Global.getInt(mContext.getContentResolver(),
CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, /* default= */ 0) == 0) {
- setSystemUserRestrictions();
+ // Only apply the system user restrictions if the system user is headless.
+ if (mCarUserManagerHelper.isHeadlessSystemUser()) {
+ setSystemUserRestrictions();
+ }
mCarUserManagerHelper.initDefaultGuestRestrictions();
Settings.Global.putInt(mContext.getContentResolver(),
CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 1);
diff --git a/tests/DirectRenderingClusterSample/Android.mk b/tests/DirectRenderingClusterSample/Android.mk
index 635858f..41d2a65 100644
--- a/tests/DirectRenderingClusterSample/Android.mk
+++ b/tests/DirectRenderingClusterSample/Android.mk
@@ -39,7 +39,8 @@
androidx-constraintlayout_constraintlayout \
androidx.car_car-cluster \
car-arch-common \
- car-media-common
+ car-media-common \
+ car-telephony-common
include $(BUILD_PACKAGE)
diff --git a/tests/DirectRenderingClusterSample/res/layout/fragment_phone.xml b/tests/DirectRenderingClusterSample/res/layout/fragment_phone.xml
index 42fe24a..b7f903b 100644
--- a/tests/DirectRenderingClusterSample/res/layout/fragment_phone.xml
+++ b/tests/DirectRenderingClusterSample/res/layout/fragment_phone.xml
@@ -1,13 +1,61 @@
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context="com.google.experiments.client.pavelm.fakeclusterux.PhoneFragment">
+ android:orientation="vertical">
- <!-- TODO: Update blank fragment layout -->
- <TextView
+ <FrameLayout
+ android:id="@+id/user_profile_container"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:text="@string/hello_blank_fragment" />
+ android:gravity="center"
+ android:layout_height="0dp"
+ android:layout_weight="1">
-</FrameLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical">
+ <ImageView
+ android:id="@+id/avatar"
+ android:layout_width="@dimen/large_avatar_icon_size"
+ android:layout_height="@dimen/large_avatar_icon_size"
+ android:layout_gravity="center"
+ android:scaleType="fitCenter" />
+ <TextView
+ android:id="@+id/title"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/user_profile_title_padding_top"
+ android:focusable="true"
+ android:maxLines="1"
+ android:gravity="center"/>
+ <TextView
+ android:id="@+id/body"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/user_profile_body_padding_top"
+ android:gravity="center"
+ android:maxLines="1"/>
+ </LinearLayout>
+
+ </FrameLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/DirectRenderingClusterSample/res/values/dimens.xml b/tests/DirectRenderingClusterSample/res/values/dimens.xml
index 9ada025..21aec67 100644
--- a/tests/DirectRenderingClusterSample/res/values/dimens.xml
+++ b/tests/DirectRenderingClusterSample/res/values/dimens.xml
@@ -88,4 +88,11 @@
<dimen name="fragment_playback_guide_margin_x">@*android:dimen/car_margin</dimen>
<dimen name="fragment_playback_guide_margin_top">@*android:dimen/car_padding_4</dimen>
+ <!-- -->
+ <!-- Communication Facet -->
+ <!-- -->
+ <dimen name="user_profile_title_padding_top">@*android:dimen/car_padding_3</dimen>
+ <dimen name="user_profile_body_padding_top">@*android:dimen/car_padding_3</dimen>
+
+ <dimen name="large_avatar_icon_size">@dimen/car_large_avatar_size</dimen>
</resources>
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/HeartBeatLiveData.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/HeartBeatLiveData.java
new file mode 100644
index 0000000..42116b8
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/HeartBeatLiveData.java
@@ -0,0 +1,57 @@
+/*
+ * 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.cluster.sample;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.lifecycle.LiveData;
+
+/**
+ * Emits a true value in a fixed periodical pace. The first beat begins when this live data becomes
+ * active.
+ *
+ * <p> Note that if this heart beat is shared, the time can be less than the given interval between
+ * observation and first beat for the second observer.
+ */
+public class HeartBeatLiveData extends LiveData<Boolean> {
+ private long mPulseRate;
+ private Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+
+ public HeartBeatLiveData(long rateInMillis) {
+ mPulseRate = rateInMillis;
+ }
+
+ @Override
+ protected void onActive() {
+ super.onActive();
+ mMainThreadHandler.post(mUpdateDurationRunnable);
+ }
+
+ @Override
+ protected void onInactive() {
+ super.onInactive();
+ mMainThreadHandler.removeCallbacks(mUpdateDurationRunnable);
+ }
+
+ private final Runnable mUpdateDurationRunnable = new Runnable() {
+ @Override
+ public void run() {
+ setValue(true);
+ mMainThreadHandler.postDelayed(this, mPulseRate);
+ }
+ };
+}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
index 9f4f931..9b0273d 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
@@ -57,6 +57,8 @@
import androidx.lifecycle.ViewModelProviders;
import androidx.viewpager.widget.ViewPager;
+import com.android.car.telephony.common.InMemoryPhoneBook;
+
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
@@ -107,6 +109,8 @@
private final Runnable mRetryLaunchNavigationActivity = this::tryLaunchNavigationActivity;
private int mNavigationDisplayId = NO_DISPLAY;
+ private int mPreviousFacet;
+
/**
* Description of a virtual display
*/
@@ -170,7 +174,7 @@
}
public void register(Context context) {
- IntentFilter intentFilter = new IntentFilter(ACTION_USER_UNLOCKED);
+ IntentFilter intentFilter = new IntentFilter(ACTION_USER_UNLOCKED);
intentFilter.addAction(ACTION_USER_SWITCHED);
context.registerReceiver(this, intentFilter);
}
@@ -235,6 +239,24 @@
mUserReceiver = new UserReceiver(this);
mUserReceiver.register(this);
+
+ InMemoryPhoneBook.init(this);
+
+ PhoneFragmentViewModel phoneViewModel = ViewModelProviders.of(this).get(
+ PhoneFragmentViewModel.class);
+
+ phoneViewModel.setPhoneStateCallback(new PhoneFragmentViewModel.PhoneStateCallback() {
+ @Override
+ public void onCall() {
+ mPreviousFacet = mPager.getCurrentItem();
+ mOrderToFacet.get(1).mButton.requestFocus();
+ }
+
+ @Override
+ public void onDisconnect() {
+ mOrderToFacet.get(mPreviousFacet).mButton.requestFocus();
+ }
+ });
}
private <V> void registerSensor(TextView textView, LiveData<V> source) {
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragment.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragment.java
index 31c165f..c546f9d 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragment.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -16,27 +16,62 @@
package android.car.cluster.sample;
import android.os.Bundle;
+import android.telephony.TelephonyManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.ViewModelProviders;
+
+import com.android.car.telephony.common.TelecomUtils;
/**
- * A simple {@link Fragment} subclass.
+ * Displays ongoing call information.
*/
public class PhoneFragment extends Fragment {
+ private View mUserProfileContainerView;
+ private PhoneFragmentViewModel mViewModel;
public PhoneFragment() {
// Required empty public constructor
}
+ @Nullable
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- // Inflate the layout for this fragment
- return inflater.inflate(R.layout.fragment_phone, container, false);
- }
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ Bundle savedInstanceState) {
+ FragmentActivity activity = requireActivity();
+ mViewModel = ViewModelProviders.of(activity).get(
+ PhoneFragmentViewModel.class);
+ View fragmentView = inflater.inflate(R.layout.fragment_phone, container, false);
+ mUserProfileContainerView = fragmentView.findViewById(R.id.user_profile_container);
+
+ TextView body = mUserProfileContainerView.findViewById(R.id.body);
+ ImageView avatar = mUserProfileContainerView.findViewById(R.id.avatar);
+ TextView nameView = mUserProfileContainerView.findViewById(R.id.title);
+
+ mViewModel.getContactInfo().observe(getViewLifecycleOwner(), (contactInfo) -> {
+ nameView.setText(contactInfo.getDisplayName());
+ TelecomUtils.setContactBitmapAsync(getContext(),
+ avatar, contactInfo.getContact(), contactInfo.getNumber());
+ });
+ mViewModel.getBody().observe(getViewLifecycleOwner(), body::setText);
+ mViewModel.getState().observe(getViewLifecycleOwner(), (state) -> {
+ if (state == TelephonyManager.CALL_STATE_IDLE) {
+ mUserProfileContainerView.setVisibility(View.GONE);
+ } else {
+ mUserProfileContainerView.setVisibility(View.VISIBLE);
+ }
+ });
+
+ return fragmentView;
+ }
}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragmentViewModel.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragmentViewModel.java
new file mode 100644
index 0000000..dd955e1
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragmentViewModel.java
@@ -0,0 +1,136 @@
+/*
+ * 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.cluster.sample;
+
+import static androidx.lifecycle.Transformations.map;
+
+import android.app.Application;
+import android.content.Context;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.android.car.telephony.common.Contact;
+import com.android.car.telephony.common.InMemoryPhoneBook;
+import com.android.car.telephony.common.TelecomUtils;
+
+/**
+ * View model for {@link PhoneFragment}
+ */
+public final class PhoneFragmentViewModel extends AndroidViewModel {
+ private MutableLiveData<Long> mConnectTime = new MutableLiveData<>();
+ private MutableLiveData<Integer> mState = new MutableLiveData<>();
+ private MutableLiveData<String> mNumber = new MutableLiveData<>();
+ private LiveData<String> mBody;
+ private LiveData<ContactInfo> mContactInfo;
+
+ private PhoneStateCallback mCallback;
+
+ public PhoneFragmentViewModel(Application application) {
+ super(application);
+
+ TelephonyManager telephonyManager = (TelephonyManager) application.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ telephonyManager.listen(new ClusterPhoneStateListener(),
+ PhoneStateListener.LISTEN_CALL_STATE);
+
+ mBody = new SelfRefreshDescriptionLiveData(getApplication(), mState, mNumber, mConnectTime);
+
+ mContactInfo = map(mNumber, (number) -> {
+ return new ContactInfo(number);
+ });
+ }
+
+ public interface PhoneStateCallback {
+ void onCall();
+
+ void onDisconnect();
+ }
+
+ public LiveData<Integer> getState() {
+ return mState;
+ }
+
+ public LiveData<String> getBody() {
+ return mBody;
+ }
+
+ public LiveData<ContactInfo> getContactInfo() {
+ return mContactInfo;
+ }
+
+ public void setPhoneStateCallback(PhoneStateCallback callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * Listens to phone state changes
+ */
+ private class ClusterPhoneStateListener extends PhoneStateListener {
+ ClusterPhoneStateListener() {
+ }
+
+ @Override
+ public void onCallStateChanged(int state, String incomingNumber) {
+ super.onCallStateChanged(state, incomingNumber);
+
+ mState.setValue(state);
+ mNumber.setValue(incomingNumber);
+
+ if (state == TelephonyManager.CALL_STATE_IDLE) {
+ if (mCallback != null) {
+ mCallback.onDisconnect();
+ }
+ } else if (state == TelephonyManager.CALL_STATE_RINGING) {
+ if (mCallback != null) {
+ mCallback.onCall();
+ }
+ } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
+ mConnectTime.setValue(System.currentTimeMillis());
+ if (mCallback != null) {
+ mCallback.onCall();
+ }
+ }
+ }
+ }
+
+ public class ContactInfo {
+ private String mNumber;
+ private String mDisplayName;
+ private Contact mContact;
+
+ public ContactInfo(String number) {
+ mNumber = number;
+ mDisplayName = TelecomUtils.getDisplayNameAndAvatarUri(getApplication(), number).first;
+ mContact = InMemoryPhoneBook.get().lookupContactEntry(number);
+ }
+
+ public String getNumber() {
+ return mNumber;
+ }
+
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ public Contact getContact() {
+ return mContact;
+ }
+ }
+}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SelfRefreshDescriptionLiveData.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SelfRefreshDescriptionLiveData.java
new file mode 100644
index 0000000..79122c4
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SelfRefreshDescriptionLiveData.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.cluster.sample;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MediatorLiveData;
+
+import com.android.car.telephony.common.TelecomUtils;
+
+/**
+ * Emits the description for the body in {@link PhoneFragmentViewModel}.
+ *
+ * This description may be the current duration of the call, call state, call type,
+ * or a combination of them.
+ *
+ * Possible strings:
+ * "Ringing"
+ * "1:05"
+ * "Mobile · Dialing"
+ * "Mobile · 1:05"
+ */
+public class SelfRefreshDescriptionLiveData extends MediatorLiveData<String> {
+ private final LiveData<Long> mConnectTimeLiveData;
+ private final LiveData<String> mNumberLiveData;
+ private final LiveData<Integer> mStateLiveData;
+ private final Context mContext;
+
+ /**
+ * @param stateLiveData LiveData holding the {@link TelephonyManager} call state
+ * @param numberLiveData LiveData holding the call number
+ * @param connectTimeLiveData LiveData holding the starting timestamp of the call
+ */
+ public SelfRefreshDescriptionLiveData(Context context,
+ LiveData<Integer> stateLiveData,
+ LiveData<String> numberLiveData,
+ LiveData<Long> connectTimeLiveData) {
+ mContext = context;
+ mNumberLiveData = numberLiveData;
+ mStateLiveData = stateLiveData;
+ mConnectTimeLiveData = connectTimeLiveData;
+
+ HeartBeatLiveData heartBeatLiveData = new HeartBeatLiveData(DateUtils.SECOND_IN_MILLIS);
+
+ addSource(stateLiveData, (trigger) -> updateDescription());
+ addSource(heartBeatLiveData, (trigger) -> updateDescription());
+ addSource(mNumberLiveData, (trigger) -> updateDescription());
+ addSource(mConnectTimeLiveData, (trigger) -> updateDescription());
+ }
+
+ private void updateDescription() {
+ String number = mNumberLiveData.getValue();
+ Integer callState = mStateLiveData.getValue();
+ Long connectTime = mConnectTimeLiveData.getValue();
+ if (callState != null) {
+ String newDescription = getCallInfoText(mContext, callState, number,
+ connectTime != null ? connectTime : 0);
+
+ String oldDescription = getValue();
+ if (!newDescription.equals(oldDescription)) {
+ setValue(newDescription);
+ }
+ } else {
+ setValue("");
+ }
+ }
+
+ /**
+ * @return A formatted string that has information about the phone call
+ * Possible strings:
+ * "Mobile · Dialing"
+ * "Mobile · 1:05"
+ */
+ private String getCallInfoText(Context context, Integer callState, String number,
+ Long connectTime) {
+ CharSequence label = TelecomUtils.getTypeFromNumber(context, number);
+ String text = "";
+ if (callState == TelephonyManager.CALL_STATE_OFFHOOK) {
+ long duration = connectTime > 0 ? System.currentTimeMillis()
+ - connectTime : 0;
+ String durationString = DateUtils.formatElapsedTime(duration / 1000);
+ if (!TextUtils.isEmpty(durationString) && !TextUtils.isEmpty(label)) {
+ text = context.getString(R.string.phone_label_with_info, label,
+ durationString);
+ } else if (!TextUtils.isEmpty(durationString)) {
+ text = durationString;
+ } else if (!TextUtils.isEmpty(label)) {
+ text = (String) label;
+ }
+ } else {
+ String state = callStateToUiString(context, callState);
+ if (!TextUtils.isEmpty(label)) {
+ text = context.getString(R.string.phone_label_with_info, label, state);
+ } else {
+ text = state;
+ }
+ }
+
+ return text;
+ }
+
+ /**
+ * @return A string representation of the call state that can be presented to a user.
+ */
+ private String callStateToUiString(Context context, int state) {
+ switch (state) {
+ case TelephonyManager.CALL_STATE_IDLE:
+ return context.getString(R.string.call_state_call_ended);
+ case TelephonyManager.CALL_STATE_RINGING:
+ return context.getString(R.string.call_state_call_ringing);
+ case TelephonyManager.CALL_STATE_OFFHOOK:
+ return context.getString(R.string.call_state_call_active);
+ default:
+ return "";
+ }
+ }
+}
diff --git a/tests/UxRestrictionsSample/Android.mk b/tests/UxRestrictionsSample/Android.mk
index 3e006b4..3092876 100644
--- a/tests/UxRestrictionsSample/Android.mk
+++ b/tests/UxRestrictionsSample/Android.mk
@@ -42,7 +42,7 @@
LOCAL_STATIC_JAVA_LIBRARIES += vehicle-hal-support-lib
LOCAL_STATIC_ANDROID_LIBRARIES += \
- androidx.legacy_legacy-support-v4 \
+ com.google.android.material_material \
androidx.appcompat_appcompat
LOCAL_JAVA_LIBRARIES += android.car
diff --git a/tests/UxRestrictionsSample/AndroidManifest.xml b/tests/UxRestrictionsSample/AndroidManifest.xml
index bd28e0d..5e1134e 100644
--- a/tests/UxRestrictionsSample/AndroidManifest.xml
+++ b/tests/UxRestrictionsSample/AndroidManifest.xml
@@ -14,11 +14,15 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.google.android.car.uxr.sample" android:sharedUserId="android.uid.system">
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.google.android.car.uxr.sample"
+ android:sharedUserId="android.uid.system">
<uses-permission android:name="android.car.permission.CAR_DRIVING_STATE"/>
- <application android:label="UxRestrictions Sample">
+ <application android:label="UxRestrictions Sample"
+ android:appComponentFactory="androidx.core.app.CoreComponentFactory"
+ tools:replace="android:appComponentFactory">
<activity android:name=".MainActivity" android:theme="@style/AppTheme" android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
diff --git a/tests/UxRestrictionsSample/res/layout/fragment_configuration_dialog.xml b/tests/UxRestrictionsSample/res/layout/fragment_configuration_dialog.xml
new file mode 100644
index 0000000..6880052
--- /dev/null
+++ b/tests/UxRestrictionsSample/res/layout/fragment_configuration_dialog.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center">
+
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:text="@string/set_uxr_config_dialog_title"
+ android:gravity="center"/>
+
+ <com.google.android.material.tabs.TabLayout
+ android:id="@+id/tab_layout"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ app:tabMode="fixed"/>
+
+ <androidx.viewpager.widget.ViewPager
+ android:id="@+id/view_pager"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+
+ <Button
+ android:id="@+id/positive_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="confirm"/>
+</LinearLayout>
diff --git a/tests/UxRestrictionsSample/res/layout/main_activity.xml b/tests/UxRestrictionsSample/res/layout/main_activity.xml
index 1d21b50..e8634ee 100644
--- a/tests/UxRestrictionsSample/res/layout/main_activity.xml
+++ b/tests/UxRestrictionsSample/res/layout/main_activity.xml
@@ -15,16 +15,10 @@
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
+ android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
-
- <LinearLayout
- android:layout_height="match_parent"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:orientation="vertical">
-
+ <!-- Section: Current Status -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -71,6 +65,7 @@
android:textSize="@dimen/info_text_size"
android:textAppearance="?android:textAppearanceLarge"/>
+ <!-- Section: Available Actions -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
@@ -88,43 +83,47 @@
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <Button
- 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"/>
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button
+ 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"/>
+ <Button
+ android:id="@+id/toggle_passenger_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/section_padding"
+ android:padding="@dimen/section_padding"
+ android:text="@string/enable_passenger_mode"
+ android:textAllCaps="false"
+ android:textSize="@dimen/info_text_size"/>
</LinearLayout>
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_marginTop="@dimen/section_padding"
- android:layout_marginBottom="10dp"
- android:background="@android:color/darker_gray"/>
-
+ <!-- Section: Save UX Restrictions For Next Boot -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
@@ -140,18 +139,13 @@
android:textSize="@dimen/header_text_size"
android:textAppearance="?android:textAppearanceLarge"/>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <Button
- 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>
- </LinearLayout>
+ <Button
+ 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 aae0e0d..c789067 100644
--- a/tests/UxRestrictionsSample/res/values/strings.xml
+++ b/tests/UxRestrictionsSample/res/values/strings.xml
@@ -25,6 +25,8 @@
<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="enable_passenger_mode" translatable="false">Enable Passenger Mode</string>
+ <string name="disable_passenger_mode" translatable="false">Disable Passenger Mode</string>
<string name="save_uxr_config_header" translatable="false"><u>Save UX Restrictions For Next Boot</u></string>
<string name="save_uxr_config" translatable="false">Save UX Restrictions</string>
<string name="set_uxr_config_dialog_title" translatable="false">Select restrictions for IDLING/MOVING</string>
@@ -34,4 +36,6 @@
<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>
+ <string name="tab_baseline" translatable="false">Baseline</string>
+ <string name="tab_passenger" translatable="false">Passenger</string>
</resources>
diff --git a/tests/UxRestrictionsSample/res/values/styles.xml b/tests/UxRestrictionsSample/res/values/styles.xml
index b752323..4ddef1f 100644
--- a/tests/UxRestrictionsSample/res/values/styles.xml
+++ b/tests/UxRestrictionsSample/res/values/styles.xml
@@ -15,7 +15,7 @@
-->
<resources>
- <style name="AppTheme" parent="@style/android:Theme.DeviceDefault.NoActionBar">
+ <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@android:color/black</item>
</style>
</resources>
\ No newline at end of file
diff --git a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/ConfigurationDialogFragment.java b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/ConfigurationDialogFragment.java
new file mode 100644
index 0000000..ab4a278
--- /dev/null
+++ b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/ConfigurationDialogFragment.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.car.uxr.sample;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.SparseBooleanArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentPagerAdapter;
+import androidx.fragment.app.ListFragment;
+import androidx.viewpager.widget.ViewPager;
+
+import com.google.android.material.tabs.TabLayout;
+
+/**
+ * DialogFragment that selects UX restrictions for configuration.
+ *
+ * <p>Supports baseline and passenger mode.
+ */
+public class ConfigurationDialogFragment extends DialogFragment {
+
+ static ConfigurationDialogFragment newInstance() {
+ return new ConfigurationDialogFragment();
+ }
+
+ /**
+ * Callback to be invoked when "confirm" button in the dialog is clicked.
+ */
+ interface OnConfirmListener {
+ /**
+ * Called when "confirm" button in the dialog is clicked.
+ *
+ * @param baseline Restrictions selected for baseline mode. See
+ * {@link CarUxRestrictions#getActiveRestrictions()}.
+ * @param passenger Restrictions selected for baseline mode.
+ */
+ void onConfirm(int baseline, int passenger);
+ }
+
+ private Button mPositiveButton;
+ private TabLayout mTabLayout;
+ private ViewPager mViewPager;
+ private UxRestrictionsListFragmentPagerAdapter mAdapter;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_configuration_dialog, container,
+ /* attachToRoot= */ false);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mViewPager = view.findViewById(R.id.view_pager);
+ mViewPager.setAdapter(mAdapter);
+
+ mTabLayout = view.findViewById(R.id.tab_layout);
+ mTabLayout.setupWithViewPager(mViewPager);
+
+ mPositiveButton = view.findViewById(R.id.positive_button);
+ mPositiveButton.setOnClickListener(v -> {
+ ((OnConfirmListener) getActivity()).onConfirm(
+ mAdapter.mBaselineRestrictions, mAdapter.mPassengerRestrictions);
+ dismiss();
+ });
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mAdapter = new UxRestrictionsListFragmentPagerAdapter(context, getChildFragmentManager());
+ }
+
+ public static class UxRestrictionsListFragment extends ListFragment {
+
+ public interface OnUxRestrictionsSelectedListener {
+ void onUxRestrictionsSelected(int restrictions);
+ }
+
+ static UxRestrictionsListFragment newInstance() {
+ return new UxRestrictionsListFragment();
+ }
+
+ /**
+ * This field translate a UX restriction value to a string name.
+ *
+ * <p>Order of strings should be fixed. Index of string is based on number of bits shifted
+ * in value of the constants. Namely, "NO_VIDEO" at index 4 maps to value of
+ * {@link android.car.drivingstate.CarUxRestrictions#UX_RESTRICTIONS_NO_VIDEO} (0x1 << 4).
+ */
+ private static final CharSequence[] UX_RESTRICTION_NAMES = new CharSequence[]{
+ "NO_DIALPAD",
+ "NO_FILTERING",
+ "LIMIT_STRING_LENGTH",
+ "NO_KEYBOARD",
+ "NO_VIDEO",
+ "LIMIT_CONTENT",
+ "NO_SETUP",
+ "NO_TEXT_MESSAGE",
+ "NO_VOICE_TRANSCRIPTION",
+ };
+ private OnUxRestrictionsSelectedListener mListener;
+
+ public void setOnUxRestrictionsSelectedListener(OnUxRestrictionsSelectedListener listener) {
+ mListener = listener;
+ }
+
+ private int getSelectedUxRestrictions() {
+ int selectedRestrictions = 0;
+ SparseBooleanArray selected = getListView().getCheckedItemPositions();
+ for (int i = 0; i < UX_RESTRICTION_NAMES.length; i++) {
+ if (selected.get(i)) {
+ selectedRestrictions += 1 << i;
+ }
+ }
+ return selectedRestrictions;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+ getListView().setOnItemClickListener((parent, v, position, id) -> {
+ if (mListener != null) {
+ mListener.onUxRestrictionsSelected(getSelectedUxRestrictions());
+ }
+ });
+ setListAdapter(new ArrayAdapter<CharSequence>(
+ getContext(),
+ android.R.layout.simple_list_item_multiple_choice,
+ UX_RESTRICTION_NAMES));
+ }
+ }
+
+ private static class UxRestrictionsListFragmentPagerAdapter extends FragmentPagerAdapter {
+ private final Context mContext;
+ int mBaselineRestrictions;
+ int mPassengerRestrictions;
+
+ private final int[] mPageTitles = new int[]{
+ R.string.tab_baseline, R.string.tab_passenger};
+
+ UxRestrictionsListFragmentPagerAdapter(Context context, FragmentManager fragmentManager) {
+ super(fragmentManager);
+ mContext = context;
+ }
+
+ @Override
+ public int getCount() {
+ return mPageTitles.length;
+ }
+
+ @Override
+ public Fragment getItem(int index) {
+ return UxRestrictionsListFragment.newInstance();
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return mContext.getString(mPageTitles[position]);
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ UxRestrictionsListFragment fragment = (UxRestrictionsListFragment)
+ super.instantiateItem(container, position);
+ switch (position) {
+ case 0:
+ fragment.setOnUxRestrictionsSelectedListener(
+ restrictions -> mBaselineRestrictions = restrictions);
+ break;
+ case 1:
+ fragment.setOnUxRestrictionsSelectedListener(
+ restrictions -> mPassengerRestrictions = restrictions);
+ break;
+ default:
+ throw new IllegalStateException("Unsupported page index " + position);
+ }
+ return fragment;
+ }
+
+ }
+}
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 60268ea..870468e 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
@@ -18,8 +18,9 @@
import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_IDLING;
import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING;
import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED;
+import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
+import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER;
-import android.app.Activity;
import android.app.AlertDialog;
import android.car.Car;
import android.car.content.pm.CarPackageManager;
@@ -27,85 +28,107 @@
import android.car.drivingstate.CarDrivingStateManager;
import android.car.drivingstate.CarUxRestrictions;
import android.car.drivingstate.CarUxRestrictionsConfiguration;
+import android.car.drivingstate.CarUxRestrictionsConfiguration.DrivingStateRestrictions;
import android.car.drivingstate.CarUxRestrictionsManager;
-import android.content.ComponentName;
-import android.content.ServiceConnection;
import android.os.Bundle;
-import android.os.IBinder;
import android.util.JsonWriter;
-import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.DialogFragment;
+
import java.io.CharArrayWriter;
/**
* Sample app that uses components in car support library to demonstrate Car drivingstate UXR
* status.
*/
-public class MainActivity extends Activity {
- public static final String TAG = "drivingstate";
+public class MainActivity extends AppCompatActivity
+ implements ConfigurationDialogFragment.OnConfirmListener {
- // Order of elements is based on number of bits shifted in value of the constants.
- private static final CharSequence[] UX_RESTRICTION_NAMES = new CharSequence[]{
- "BASELINE",
- "NO_DIALPAD",
- "NO_FILTERING",
- "LIMIT_STRING_LENGTH",
- "NO_KEYBOARD",
- "NO_VIDEO",
- "LIMIT_CONTENT",
- "NO_SETUP",
- "NO_TEXT_MESSAGE",
- "NO_VOICE_TRANSCRIPTION",
- };
+ public static final String TAG = "UxRDemo";
+
+ private static final String DIALOG_FRAGMENT_TAG = "dialog_fragment_tag";
private Car mCar;
private CarDrivingStateManager mCarDrivingStateManager;
private CarUxRestrictionsManager mCarUxRestrictionsManager;
private CarPackageManager mCarPackageManager;
+
+ private CarUxRestrictionsManager.OnUxRestrictionsChangedListener mUxRChangeListener =
+ this::updateUxRText;
+ private CarDrivingStateManager.CarDrivingStateEventListener mDrvStateChangeListener =
+ this::updateDrivingStateText;
+
private TextView mDrvStatus;
private TextView mDistractionOptStatus;
private TextView mUxrStatus;
private Button mToggleButton;
- private Button mSampleMsgButton;
private Button mSaveUxrConfigButton;
private Button mShowStagedConfig;
private Button mShowProdConfig;
+ private Button mTogglePassengerMode;
private boolean mEnableUxR;
- private final ServiceConnection mCarConnectionListener =
- new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder iBinder) {
- Log.d(TAG, "Connected to " + name.flattenToString());
- // Get Driving State & UXR manager
- mCarDrivingStateManager = (CarDrivingStateManager) mCar.getCarManager(
- Car.CAR_DRIVING_STATE_SERVICE);
- mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager(
- Car.CAR_UX_RESTRICTION_SERVICE);
- mCarPackageManager = (CarPackageManager) mCar.getCarManager(
- Car.PACKAGE_SERVICE);
- if (mCarDrivingStateManager != null) {
- mCarDrivingStateManager.registerListener(mDrvStateChangeListener);
- updateDrivingStateText(
- mCarDrivingStateManager.getCurrentCarDrivingState());
- }
- if (mCarUxRestrictionsManager != null) {
- mCarUxRestrictionsManager.registerListener(mUxRChangeListener);
- updateUxRText(mCarUxRestrictionsManager.getCurrentCarUxRestrictions());
- }
- }
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
- @Override
- public void onServiceDisconnected(ComponentName name) {
- Log.d(TAG, "Disconnected from " + name.flattenToString());
- mCarDrivingStateManager = null;
- mCarUxRestrictionsManager = null;
- mCarPackageManager = null;
- }
- };
+ setContentView(R.layout.main_activity);
+
+ mDrvStatus = findViewById(R.id.driving_state);
+ mDistractionOptStatus = findViewById(R.id.do_status);
+ mUxrStatus = findViewById(R.id.uxr_status);
+
+ mToggleButton = findViewById(R.id.toggle_status);
+ mToggleButton.setOnClickListener(v -> updateToggleUxREnable());
+
+ mSaveUxrConfigButton = findViewById(R.id.save_uxr_config);
+ mSaveUxrConfigButton.setOnClickListener(v -> showConfigurationDialog());
+
+ mShowStagedConfig = findViewById(R.id.show_staged_config);
+ mShowStagedConfig.setOnClickListener(v -> showStagedUxRestrictionsConfig());
+ mShowProdConfig = findViewById(R.id.show_prod_config);
+ mShowProdConfig.setOnClickListener(v -> showProdUxRestrictionsConfig());
+
+ mTogglePassengerMode = findViewById(R.id.toggle_passenger_mode);
+ mTogglePassengerMode.setOnClickListener(v -> togglePassengerMode());
+
+ // Connect to car service
+ mCar = Car.createCar(this);
+
+ mCarDrivingStateManager = (CarDrivingStateManager) mCar.getCarManager(
+ Car.CAR_DRIVING_STATE_SERVICE);
+ mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager(
+ Car.CAR_UX_RESTRICTION_SERVICE);
+ mCarPackageManager = (CarPackageManager) mCar.getCarManager(
+ Car.PACKAGE_SERVICE);
+ if (mCarDrivingStateManager != null) {
+ mCarDrivingStateManager.registerListener(mDrvStateChangeListener);
+ updateDrivingStateText(
+ mCarDrivingStateManager.getCurrentCarDrivingState());
+ }
+ if (mCarUxRestrictionsManager != null) {
+ mCarUxRestrictionsManager.registerListener(mUxRChangeListener);
+ updateUxRText(mCarUxRestrictionsManager.getCurrentCarUxRestrictions());
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mCarUxRestrictionsManager != null) {
+ mCarUxRestrictionsManager.unregisterListener();
+ }
+ if (mCarDrivingStateManager != null) {
+ mCarDrivingStateManager.unregisterListener();
+ }
+ if (mCar != null) {
+ mCar.disconnect();
+ }
+ }
private void updateUxRText(CarUxRestrictions restrictions) {
mDistractionOptStatus.setText(
@@ -159,64 +182,54 @@
mDrvStatus.requestLayout();
}
- private CarUxRestrictionsManager.OnUxRestrictionsChangedListener mUxRChangeListener =
- this::updateUxRText;
+ private void togglePassengerMode() {
+ if (mCarUxRestrictionsManager == null) {
+ return;
+ }
+ int mode = mCarUxRestrictionsManager.getRestrictionMode();
+ switch (mode) {
+ case UX_RESTRICTION_MODE_BASELINE:
+ mCarUxRestrictionsManager.setRestrictionMode(UX_RESTRICTION_MODE_PASSENGER);
+ mTogglePassengerMode.setText(R.string.disable_passenger_mode);
+ break;
+ case UX_RESTRICTION_MODE_PASSENGER:
+ mCarUxRestrictionsManager.setRestrictionMode(UX_RESTRICTION_MODE_BASELINE);
+ mTogglePassengerMode.setText(R.string.enable_passenger_mode);
+ break;
+ default:
+ throw new IllegalStateException("Unrecognized restriction mode " + mode);
+ }
+ }
- private CarDrivingStateManager.CarDrivingStateEventListener mDrvStateChangeListener =
- this::updateDrivingStateText;
+ private void showConfigurationDialog() {
+ DialogFragment dialogFragment = ConfigurationDialogFragment.newInstance();
+ dialogFragment.show(getSupportFragmentManager(), DIALOG_FRAGMENT_TAG);
+ }
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ public void onConfirm(int baseline, int passenger) {
- setContentView(R.layout.main_activity);
-
- mDrvStatus = findViewById(R.id.driving_state);
- mDistractionOptStatus = findViewById(R.id.do_status);
- mUxrStatus = findViewById(R.id.uxr_status);
- mToggleButton = findViewById(R.id.toggle_status);
-
- 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());
-
- // Connect to car service
- mCar = Car.createCar(this, mCarConnectionListener);
- mCar.connect();
- }
-
- private void saveUxrConfig() {
- // Pop up a dialog to build the IDLING restrictions.
- boolean[] selected = new boolean[UX_RESTRICTION_NAMES.length];
- new AlertDialog.Builder(this)
- .setTitle(R.string.set_uxr_config_dialog_title)
- .setMultiChoiceItems(UX_RESTRICTION_NAMES, null,
- (dialog, which, isChecked) -> selected[which] = isChecked)
- .setPositiveButton(R.string.set_uxr_config_dialog_positive_button,
- (dialog, id) -> setUxRestrictionsConfig(selected))
- .setNegativeButton(R.string.set_uxr_config_dialog_negative_button, null)
- .show();
- }
-
- private void setUxRestrictionsConfig(boolean[] selected) {
- int selectedRestrictions = 0;
- // Iteration starts at 1 because 0 is BASELINE (no restrictions).
- for (int i = 1; i < selected.length; i++) {
- if (selected[i]) {
- selectedRestrictions += 1 << (i - 1);
- }
- }
- boolean reqOpt = selectedRestrictions != 0;
CarUxRestrictionsConfiguration config = new CarUxRestrictionsConfiguration.Builder()
.setUxRestrictions(DRIVING_STATE_PARKED, false, 0)
- .setUxRestrictions(DRIVING_STATE_IDLING, reqOpt, selectedRestrictions)
- .setUxRestrictions(DRIVING_STATE_MOVING, reqOpt, selectedRestrictions)
+ .setUxRestrictions(DRIVING_STATE_MOVING,
+ new DrivingStateRestrictions()
+ .setDistractionOptimizationRequired(baseline != 0)
+ .setRestrictions(baseline))
+ .setUxRestrictions(DRIVING_STATE_MOVING,
+ new DrivingStateRestrictions()
+ .setMode(CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER)
+ .setDistractionOptimizationRequired(passenger != 0)
+ .setRestrictions(passenger))
+ .setUxRestrictions(DRIVING_STATE_IDLING,
+ new DrivingStateRestrictions()
+ .setDistractionOptimizationRequired(baseline != 0)
+ .setRestrictions(baseline))
+ .setUxRestrictions(DRIVING_STATE_IDLING,
+ new DrivingStateRestrictions()
+ .setMode(CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER)
+ .setDistractionOptimizationRequired(passenger != 0)
+ .setRestrictions(passenger))
.build();
mCarUxRestrictionsManager.saveUxRestrictionsConfigurationForNextBoot(config);
@@ -247,8 +260,7 @@
private void showProdUxRestrictionsConfig() {
try {
- CarUxRestrictionsConfiguration prodConfig =
- mCarUxRestrictionsManager.getConfig();
+ CarUxRestrictionsConfiguration prodConfig = mCarUxRestrictionsManager.getConfig();
if (prodConfig == null) {
new AlertDialog.Builder(this)
.setMessage(R.string.no_prod_config)
@@ -267,19 +279,4 @@
e.printStackTrace();
}
}
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (mCarUxRestrictionsManager != null) {
- mCarUxRestrictionsManager.unregisterListener();
- }
- if (mCarDrivingStateManager != null) {
- mCarDrivingStateManager.unregisterListener();
- }
- if (mCar != null) {
- mCar.disconnect();
- }
- }
}
-
diff --git a/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java b/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java
index 906192a..4899246 100644
--- a/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java
@@ -22,7 +22,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.os.Bundle;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
@@ -142,8 +141,8 @@
public static class ActivityThatFinishesImmediately extends Activity {
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ protected void onResume() {
+ super.onResume();
finish();
}
}
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 ebeef0d..c81f887 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
@@ -57,8 +57,8 @@
*
* The following mocks are used:
* <ol>
- * <li> {@link Context} provides system services and resources.
- * <li> {@link CarUserManagerHelper} provides user info and actions.
+ * <li> {@link Context} provides system services and resources.
+ * <li> {@link CarUserManagerHelper} provides user info and actions.
* <ol/>
*/
@RunWith(AndroidJUnit4.class)
@@ -134,14 +134,16 @@
}
/**
- * Test that the {@link CarUserService} disable modify account for user 0 upon user 0 unlock.
+ * Test that the {@link CarUserService} does set the disable modify account permission for
+ * user 0 upon user 0 unlock when user 0 is headless.
*/
@Test
- public void testDisableModifyAccountsForSystemUserOnFirstRun() {
+ public void testDisableModifyAccountsForHeadlessSystemUserOnFirstRun() {
// Mock system user.
UserInfo systemUser = new UserInfo();
systemUser.id = UserHandle.USER_SYSTEM;
doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
+ doReturn(true).when(mCarUserManagerHelper).isHeadlessSystemUser();
mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
@@ -150,6 +152,24 @@
}
/**
+ * Test that the {@link CarUserService} does not set the disable modify account permission for
+ * user 0 upon user 0 unlock when user 0 is not headless.
+ */
+ @Test
+ public void testDisableModifyAccountsForRegularSystemUserOnFirstRun() {
+ // Mock system user.
+ UserInfo systemUser = new UserInfo();
+ systemUser.id = UserHandle.USER_SYSTEM;
+ doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
+ doReturn(false).when(mCarUserManagerHelper).isHeadlessSystemUser();
+
+ mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+
+ verify(mCarUserManagerHelper, never())
+ .setUserRestriction(systemUser, UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+ }
+
+ /**
* Test that the {@link CarUserService} does not set restrictions on user 0 if they have already
* been set.
*/
@@ -168,10 +188,12 @@
}
/**
- * Test that the {@link CarUserService} disable location service for user 0 upon first run.
+ * Test that the {@link CarUserService} disables the location service for headless user 0 upon
+ * first run.
*/
@Test
- public void testDisableLocationForSystemUserOnFirstRun() {
+ public void testDisableLocationForHeadlessSystemUserOnFirstRun() {
+ doReturn(true).when(mCarUserManagerHelper).isHeadlessSystemUser();
mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
verify(mLocationManager).setLocationEnabledForUser(
@@ -179,6 +201,19 @@
}
/**
+ * Test that the {@link CarUserService} does not disable the location service for regular user 0
+ * upon first run.
+ */
+ @Test
+ public void testDisableLocationForRegularSystemUserOnFirstRun() {
+ doReturn(false).when(mCarUserManagerHelper).isHeadlessSystemUser();
+ mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+
+ verify(mLocationManager, never()).setLocationEnabledForUser(
+ /* enabled= */ false, UserHandle.of(UserHandle.USER_SYSTEM));
+ }
+
+ /**
* Test that the {@link CarUserService} updates last active user on user switch intent.
*/
@Test
@@ -263,7 +298,7 @@
// user 2 background, ignore in restart list
mCarUserService.setUserLockStatus(user2, true);
mCarUserService.setUserLockStatus(user1, false);
- assertEquals(new Integer[]{ user1 },
+ assertEquals(new Integer[]{user1},
mCarUserService.getBackgroundUsersToRestart().toArray());
doReturn(user3).when(mCarUserManagerHelper).getCurrentForegroundUserId();
@@ -313,10 +348,10 @@
doReturn(true).when(mMockedIActivityManager).startUserInBackground(user2);
doReturn(true).when(mMockedIActivityManager).unlockUser(user2,
null, null, null);
- assertEquals(new Integer[] { user2 },
+ assertEquals(new Integer[]{user2},
mCarUserService.startAllBackgroundUsers().toArray());
mCarUserService.setUserLockStatus(user2, true);
- assertEquals(new Integer[] { user3, user2 },
+ assertEquals(new Integer[]{user3, user2},
mCarUserService.getBackgroundUsersToRestart().toArray());
doReturn(ActivityManager.USER_OP_SUCCESS).when(mMockedIActivityManager).stopUser(user2,
@@ -324,10 +359,10 @@
// should not stop the current fg user
assertFalse(mCarUserService.stopBackgroundUser(user3));
assertTrue(mCarUserService.stopBackgroundUser(user2));
- assertEquals(new Integer[] { user3, user2 },
+ assertEquals(new Integer[]{user3, user2},
mCarUserService.getBackgroundUsersToRestart().toArray());
mCarUserService.setUserLockStatus(user2, false);
- assertEquals(new Integer[] { user3, user2 },
+ assertEquals(new Integer[]{user3, user2},
mCarUserService.getBackgroundUsersToRestart().toArray());
}