Add API to communicate if the car supports AppDrivenRefresh
Relnote: New Api to allow users detect if an OEM has enabled App Driven Refresh
Bug:b/243957836
Video: go/a4c-refresh-video
Test: Unit + Manual
Change-Id: I174d5a0b7cec1c41a0615d9265ee2a0101a8b27a
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/common/SamplePlaces.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/common/SamplePlaces.java
index 34c1e31..ac18ac8 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/common/SamplePlaces.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/common/SamplePlaces.java
@@ -46,6 +46,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Random;
/** Provides sample place data used in the demos. */
public class SamplePlaces {
@@ -242,7 +243,7 @@
/** Return the {@link ItemList} of the sample places. */
@NonNull
- public ItemList getPlaceList() {
+ public ItemList getPlaceList(boolean randomOrder) {
ItemList.Builder listBuilder = new ItemList.Builder();
int listLimit = 6;
@@ -258,8 +259,13 @@
listLimit = min(listLimit, mPlaces.size());
for (int index = 0; index < listLimit; index++) {
- PlaceInfo place = mPlaces.get(index);
-
+ Random rand = new Random();
+ PlaceInfo place;
+ if (randomOrder) {
+ place = mPlaces.get(rand.nextInt(listLimit));
+ } else {
+ place = mPlaces.get(index);
+ }
// Build a description string that includes the required distance span.
int distanceKm = getDistanceFromCurrentLocation(place.location) / 1000;
SpannableString description = new SpannableString(" \u00b7 " + place.description);
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/PlaceListNavigationTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/PlaceListNavigationTemplateDemoScreen.java
index 0db39ba..8f12eec 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/PlaceListNavigationTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/PlaceListNavigationTemplateDemoScreen.java
@@ -18,10 +18,14 @@
import static androidx.car.app.CarToast.LENGTH_SHORT;
+import android.os.Handler;
+import android.os.Looper;
+
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.CarToast;
import androidx.car.app.Screen;
+import androidx.car.app.constraints.ConstraintManager;
import androidx.car.app.model.Action;
import androidx.car.app.model.ActionStrip;
import androidx.car.app.model.CarIcon;
@@ -39,14 +43,27 @@
private boolean mIsFavorite = false;
+ private boolean mIsAppRefresh = false;
+
public PlaceListNavigationTemplateDemoScreen(@NonNull CarContext carContext) {
super(carContext);
mPlaces = SamplePlaces.create(this);
}
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
@NonNull
@Override
public Template onGetTemplate() {
+ boolean isAppDrivenRefreshEnabled = this.getCarContext().getCarService(
+ ConstraintManager.class).isAppDrivenRefreshEnabled();
+
+ if (isAppDrivenRefreshEnabled && !mIsAppRefresh) {
+ mIsAppRefresh = true;
+ for (int i = 1; i <= 10; i++) {
+ mHandler.postDelayed(this::invalidate, i * 1000L);
+ }
+ }
Header header = new Header.Builder()
.setStartHeaderAction(Action.BACK)
.addEndHeaderAction(new Action.Builder()
@@ -73,7 +90,7 @@
})
.build())
.addEndHeaderAction(new Action.Builder()
- .setOnClickListener(() -> finish())
+ .setOnClickListener(this::finish)
.setIcon(
new CarIcon.Builder(
IconCompat.createWithResource(
@@ -83,9 +100,9 @@
.build())
.setTitle(getCarContext().getString(R.string.place_list_nav_template_demo_title))
.build();
-
+ //Return elements in random order.
return new PlaceListNavigationTemplate.Builder()
- .setItemList(mPlaces.getPlaceList())
+ .setItemList(mPlaces.getPlaceList(/*randomOrder=*/isAppDrivenRefreshEnabled))
.setHeader(header)
.setMapActionStrip(RoutingDemoModels.getMapActionStrip(getCarContext()))
.setActionStrip(
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/templates/PlaceListTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/templates/PlaceListTemplateDemoScreen.java
index a64bf4a..9d257bb 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/templates/PlaceListTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/templates/PlaceListTemplateDemoScreen.java
@@ -38,7 +38,7 @@
@Override
public Template onGetTemplate() {
return new PlaceListMapTemplate.Builder()
- .setItemList(mPlaces.getPlaceList())
+ .setItemList(mPlaces.getPlaceList(/*randomOrder=*/false))
.setTitle(getCarContext().getString(R.string.place_list_template_demo_title))
.setHeaderAction(Action.BACK)
.build();
diff --git a/car/app/app/api/public_plus_experimental_1.3.0-beta02.txt b/car/app/app/api/public_plus_experimental_1.3.0-beta02.txt
index 108129b..547d522 100644
--- a/car/app/app/api/public_plus_experimental_1.3.0-beta02.txt
+++ b/car/app/app/api/public_plus_experimental_1.3.0-beta02.txt
@@ -228,6 +228,7 @@
@androidx.car.app.annotations.RequiresCarApi(2) public class ConstraintManager implements androidx.car.app.managers.Manager {
method public int getContentLimit(int);
+ method @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(6) public boolean isAppDrivenRefreshEnabled();
field public static final int CONTENT_LIMIT_TYPE_GRID = 1; // 0x1
field public static final int CONTENT_LIMIT_TYPE_LIST = 0; // 0x0
field public static final int CONTENT_LIMIT_TYPE_PANE = 4; // 0x4
diff --git a/car/app/app/api/public_plus_experimental_current.txt b/car/app/app/api/public_plus_experimental_current.txt
index 108129b..547d522 100644
--- a/car/app/app/api/public_plus_experimental_current.txt
+++ b/car/app/app/api/public_plus_experimental_current.txt
@@ -228,6 +228,7 @@
@androidx.car.app.annotations.RequiresCarApi(2) public class ConstraintManager implements androidx.car.app.managers.Manager {
method public int getContentLimit(int);
+ method @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(6) public boolean isAppDrivenRefreshEnabled();
field public static final int CONTENT_LIMIT_TYPE_GRID = 1; // 0x1
field public static final int CONTENT_LIMIT_TYPE_LIST = 0; // 0x0
field public static final int CONTENT_LIMIT_TYPE_PANE = 4; // 0x4
diff --git a/car/app/app/src/main/aidl/androidx/car/app/constraints/IConstraintHost.aidl b/car/app/app/src/main/aidl/androidx/car/app/constraints/IConstraintHost.aidl
index 3f1b7d4..0e058af 100644
--- a/car/app/app/src/main/aidl/androidx/car/app/constraints/IConstraintHost.aidl
+++ b/car/app/app/src/main/aidl/androidx/car/app/constraints/IConstraintHost.aidl
@@ -24,4 +24,9 @@
* Queries the host for the limit for a content type.
*/
int getContentLimit(int contentType) = 1;
+
+ /**
+ * Queries the host for the ability to support App Driven Refresh.
+ */
+ boolean isAppDrivenRefreshEnabled() = 2;
}
diff --git a/car/app/app/src/main/java/androidx/car/app/constraints/ConstraintManager.java b/car/app/app/src/main/java/androidx/car/app/constraints/ConstraintManager.java
index 4e025f3..121dc31 100644
--- a/car/app/app/src/main/java/androidx/car/app/constraints/ConstraintManager.java
+++ b/car/app/app/src/main/java/androidx/car/app/constraints/ConstraintManager.java
@@ -31,6 +31,7 @@
import androidx.car.app.HostDispatcher;
import androidx.car.app.HostException;
import androidx.car.app.R;
+import androidx.car.app.annotations.ExperimentalCarApi;
import androidx.car.app.annotations.RequiresCarApi;
import androidx.car.app.managers.Manager;
import androidx.car.app.utils.LogTags;
@@ -120,9 +121,8 @@
// TODO(b/185805900): consider caching these values if performance is a concern.
limit = mHostDispatcher.dispatchForResult(
CarContext.CONSTRAINT_SERVICE,
- "getContentLimit", (IConstraintHost host) -> {
- return host.getContentLimit(contentLimitType);
- }
+ "getContentLimit",
+ (IConstraintHost host) -> host.getContentLimit(contentLimitType)
);
} catch (RemoteException e) {
// The host is dead, don't crash the app, just log.
@@ -166,7 +166,29 @@
return new ConstraintManager(requireNonNull(context), requireNonNull(hostDispatcher));
}
- private ConstraintManager(CarContext context, HostDispatcher hostDispatcher) {
+ /**
+ * Determines if the app supports app Driven Refresh Enabled
+ */
+ @RequiresCarApi(6)
+ @ExperimentalCarApi
+ public boolean isAppDrivenRefreshEnabled() {
+ Boolean result;
+ try {
+ // TODO(b/185805900): consider caching these values if performance is a concern.
+ result = mHostDispatcher.dispatchForResult(
+ CarContext.CONSTRAINT_SERVICE,
+ "isAppDrivenRefreshEnabled", IConstraintHost::isAppDrivenRefreshEnabled
+ );
+ return Boolean.TRUE.equals(result);
+ } catch (RemoteException e) {
+ // The host is dead, don't crash the app, just log.
+ Log.w(LogTags.TAG, "Failed to retrieve list limit from the host, using defaults", e);
+ }
+ // Returns default values as documented if host call failed.
+ return false;
+ }
+
+ private ConstraintManager(@NonNull CarContext context, @NonNull HostDispatcher hostDispatcher) {
mCarContext = context;
mHostDispatcher = hostDispatcher;
}
diff --git a/car/app/app/src/test/java/androidx/car/app/constraints/ConstraintManagerTest.java b/car/app/app/src/test/java/androidx/car/app/constraints/ConstraintManagerTest.java
index f152848..1329d7d 100644
--- a/car/app/app/src/test/java/androidx/car/app/constraints/ConstraintManagerTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/constraints/ConstraintManagerTest.java
@@ -72,6 +72,11 @@
public int getContentLimit(int contentType) throws RemoteException {
return mMockConstraintHost.getContentLimit(contentType);
}
+
+ @Override
+ public boolean isAppDrivenRefreshEnabled() throws RemoteException {
+ return mMockConstraintHost.isAppDrivenRefreshEnabled();
+ }
};
when(mMockCarHost.getHost(any())).thenReturn(hostStub.asBinder());
mHostDispatcher.setCarHost(mMockCarHost);
@@ -91,17 +96,17 @@
}
@Test
- public void host_returnLimits() throws RemoteException {
- when(mMockConstraintHost.getContentLimit(CONTENT_LIMIT_TYPE_LIST)).thenReturn(1);
- when(mMockConstraintHost.getContentLimit(CONTENT_LIMIT_TYPE_GRID)).thenReturn(2);
- when(mMockConstraintHost.getContentLimit(CONTENT_LIMIT_TYPE_PLACE_LIST)).thenReturn(3);
- when(mMockConstraintHost.getContentLimit(CONTENT_LIMIT_TYPE_ROUTE_LIST)).thenReturn(4);
- when(mMockConstraintHost.getContentLimit(CONTENT_LIMIT_TYPE_PANE)).thenReturn(5);
+ public void host_throwsException_returnsDefault() throws RemoteException {
+ when(mMockConstraintHost.isAppDrivenRefreshEnabled()).thenThrow(new RemoteException());
- assertThat(mConstraintManager.getContentLimit(CONTENT_LIMIT_TYPE_LIST)).isEqualTo(1);
- assertThat(mConstraintManager.getContentLimit(CONTENT_LIMIT_TYPE_GRID)).isEqualTo(2);
- assertThat(mConstraintManager.getContentLimit(CONTENT_LIMIT_TYPE_PLACE_LIST)).isEqualTo(3);
- assertThat(mConstraintManager.getContentLimit(CONTENT_LIMIT_TYPE_ROUTE_LIST)).isEqualTo(4);
- assertThat(mConstraintManager.getContentLimit(CONTENT_LIMIT_TYPE_PANE)).isEqualTo(5);
+ assertThat(mConstraintManager.isAppDrivenRefreshEnabled()).isFalse();
+ }
+
+ @Test
+ public void host_returAppDrivenRefreshEnabled() throws RemoteException {
+ when(mMockConstraintHost.isAppDrivenRefreshEnabled()).thenReturn(true);
+
+
+ assertThat(mConstraintManager.isAppDrivenRefreshEnabled()).isTrue();
}
}