[go: nahoru, domu]

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();
     }
 }