Add suitability status to routing sample app

Bug: b/319645714
Test: Manually using the app on devices with and without new APIs.
Change-Id: Ic8eba6db99db173a95f2ebb9f6d72aa4968cf619
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/SystemRouteItem.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/SystemRouteItem.java
index c92ced8..40f1308 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/SystemRouteItem.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/SystemRouteItem.java
@@ -19,71 +19,32 @@
 import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import java.util.Objects;
 
-import javax.annotation.Nullable;
-
-/**
- * An abstract model that holds information about routes from different sources.
- *
- * Can represent media routers' routes, bluetooth routes, or audio routes.
- */
+/** Holds information about a system route. */
 public final class SystemRouteItem implements SystemRoutesAdapterItem {
 
-    @NonNull
-    private final String mId;
+    @NonNull public final String mId;
 
-    @NonNull
-    private final String mName;
+    @NonNull public final String mName;
 
-    @Nullable
-    private final String mAddress;
+    @Nullable public final String mAddress;
 
-    @Nullable
-    private final String mDescription;
+    @Nullable public final String mDescription;
+
+    @Nullable public final String mSuitabilityStatus;
+
+    @Nullable public final Boolean mTransferInitiatedBySelf;
 
     private SystemRouteItem(@NonNull Builder builder) {
-        Objects.requireNonNull(builder.mId);
-        Objects.requireNonNull(builder.mName);
-
-        mId = builder.mId;
-        mName = builder.mName;
-
+        mId = Objects.requireNonNull(builder.mId);
+        mName = Objects.requireNonNull(builder.mName);
         mAddress = builder.mAddress;
         mDescription = builder.mDescription;
-    }
-
-    /**
-     * Returns a unique identifier of a route.
-     */
-    @NonNull
-    public String getId() {
-        return mId;
-    }
-
-    /**
-     * Returns a human-readable name of the route.
-     */
-    @NonNull
-    public String getName() {
-        return mName;
-    }
-
-    /**
-     * Returns address if the route is a Bluetooth route and {@code null} otherwise.
-     */
-    @Nullable
-    public String getAddress() {
-        return mAddress;
-    }
-
-    /**
-     * Returns a route description or {@code null} if empty.
-     */
-    @Nullable
-    public String getDescription() {
-        return mDescription;
+        mSuitabilityStatus = builder.mSuitabilityStatus;
+        mTransferInitiatedBySelf = builder.mTransferInitiatedBySelf;
     }
 
     @Override
@@ -106,17 +67,12 @@
      */
     public static final class Builder {
 
-        @NonNull
-        private final String mId;
-
-        @NonNull
-        private String mName;
-
-        @Nullable
-        private String mAddress;
-
-        @Nullable
-        private String mDescription;
+        @NonNull private final String mId;
+        @NonNull private String mName;
+        @Nullable private String mAddress;
+        @Nullable private String mDescription;
+        @Nullable private String mSuitabilityStatus;
+        @Nullable private Boolean mTransferInitiatedBySelf;
 
         public Builder(@NonNull String id) {
             mId = id;
@@ -154,6 +110,26 @@
         }
 
         /**
+         * Sets a human-readable string describing the transfer suitability of the route, or null if
+         * not applicable.
+         */
+        @NonNull
+        public Builder setSuitabilityStatus(@Nullable String suitabilityStatus) {
+            mSuitabilityStatus = suitabilityStatus;
+            return this;
+        }
+
+        /**
+         * Sets whether the corresponding route's selection is the result of an action of this app,
+         * or null if not applicable.
+         */
+        @NonNull
+        public Builder setTransferInitiatedBySelf(@Nullable Boolean transferInitiatedBySelf) {
+            mTransferInitiatedBySelf = transferInitiatedBySelf;
+            return this;
+        }
+
+        /**
          * Builds {@link SystemRouteItem}.
          */
         @NonNull
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/SystemRoutesAdapter.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/SystemRoutesAdapter.java
index aba49a1..f39259f 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/SystemRoutesAdapter.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/SystemRoutesAdapter.java
@@ -20,6 +20,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -114,6 +115,8 @@
         private final AppCompatTextView mRouteIdTextView;
         private final AppCompatTextView mRouteAddressTextView;
         private final AppCompatTextView mRouteDescriptionTextView;
+        private final AppCompatTextView mSuitabilityStatusTextView;
+        private final AppCompatTextView mTransferInitiatedBySelfTextView;
 
         ItemViewHolder(@NonNull View itemView) {
             super(itemView);
@@ -122,21 +125,22 @@
             mRouteIdTextView = itemView.findViewById(R.id.route_id);
             mRouteAddressTextView = itemView.findViewById(R.id.route_address);
             mRouteDescriptionTextView = itemView.findViewById(R.id.route_description);
+            mSuitabilityStatusTextView = itemView.findViewById(R.id.route_suitability_status);
+            mTransferInitiatedBySelfTextView =
+                    itemView.findViewById(R.id.route_transfer_initiated_by_self);
         }
 
         void bind(SystemRouteItem systemRouteItem) {
-            mRouteNameTextView.setText(systemRouteItem.getName());
-            mRouteIdTextView.setText(systemRouteItem.getId());
-
-            showViewIfNotNull(mRouteAddressTextView, systemRouteItem.getAddress());
-            if (systemRouteItem.getAddress() != null) {
-                mRouteAddressTextView.setText(systemRouteItem.getAddress());
-            }
-
-            showViewIfNotNull(mRouteDescriptionTextView, systemRouteItem.getDescription());
-            if (systemRouteItem.getDescription() != null) {
-                mRouteDescriptionTextView.setText(systemRouteItem.getDescription());
-            }
+            mRouteNameTextView.setText(systemRouteItem.mName);
+            mRouteIdTextView.setText(systemRouteItem.mId);
+            setTextOrHide(mRouteAddressTextView, systemRouteItem.mAddress);
+            setTextOrHide(mRouteDescriptionTextView, systemRouteItem.mDescription);
+            setTextOrHide(mSuitabilityStatusTextView, systemRouteItem.mSuitabilityStatus);
+            String initiatedBySelfText =
+                    systemRouteItem.mTransferInitiatedBySelf != null
+                            ? "self-initiated: " + systemRouteItem.mTransferInitiatedBySelf
+                            : null;
+            setTextOrHide(mTransferInitiatedBySelfTextView, initiatedBySelfText);
         }
     }
 
@@ -145,8 +149,7 @@
         public boolean areItemsTheSame(@NonNull SystemRoutesAdapterItem oldItem,
                 @NonNull SystemRoutesAdapterItem newItem) {
             if (oldItem instanceof SystemRouteItem && newItem instanceof SystemRouteItem) {
-                return ((SystemRouteItem) oldItem).getId().equals(
-                        ((SystemRouteItem) newItem).getId());
+                return ((SystemRouteItem) oldItem).mId.equals(((SystemRouteItem) newItem).mId);
             } else if (oldItem instanceof SystemRoutesSourceItem
                     && newItem instanceof SystemRoutesSourceItem) {
                 return oldItem.equals(newItem);
@@ -169,11 +172,12 @@
         }
     }
 
-    private static <T, V extends View> void showViewIfNotNull(@NonNull V view, @Nullable T obj) {
-        if (obj == null) {
+    private static void setTextOrHide(@NonNull TextView view, @Nullable String text) {
+        if (text == null) {
             view.setVisibility(View.GONE);
         } else {
             view.setVisibility(View.VISIBLE);
+            view.setText(text);
         }
     }
 }
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/source/MediaRouter2SystemRoutesSource.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/source/MediaRouter2SystemRoutesSource.java
index 5785679..df60049 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/source/MediaRouter2SystemRoutesSource.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/source/MediaRouter2SystemRoutesSource.java
@@ -16,6 +16,7 @@
 
 package com.example.androidx.mediarouting.activities.systemrouting.source;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2;
@@ -28,6 +29,8 @@
 import com.example.androidx.mediarouting.activities.systemrouting.SystemRouteItem;
 import com.example.androidx.mediarouting.activities.systemrouting.SystemRoutesSourceItem;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -38,11 +41,9 @@
 @RequiresApi(Build.VERSION_CODES.R)
 public final class MediaRouter2SystemRoutesSource extends SystemRoutesSource {
 
-    @NonNull
-    private Context mContext;
-    @NonNull
-    private MediaRouter2 mMediaRouter2;
-
+    @NonNull private final Context mContext;
+    @NonNull private final MediaRouter2 mMediaRouter2;
+    @NonNull private final Method mSuitabilityStatusMethod;
     @NonNull
     private final Map<String, MediaRoute2Info> mLastKnownRoutes = new HashMap<>();
     @NonNull
@@ -81,6 +82,16 @@
             @NonNull MediaRouter2 mediaRouter2) {
         mContext = context;
         mMediaRouter2 = mediaRouter2;
+
+        Method suitabilityStatusMethod = null;
+        // TODO: b/336510942 - Remove reflection once these APIs are available in
+        // androidx-platform-dev.
+        try {
+            suitabilityStatusMethod =
+                    MediaRoute2Info.class.getDeclaredMethod("getSuitabilityStatus");
+        } catch (NoSuchMethodException | IllegalAccessError e) {
+        }
+        mSuitabilityStatusMethod = suitabilityStatusMethod;
     }
 
     @Override
@@ -127,10 +138,37 @@
     }
 
     @NonNull
-    private static SystemRouteItem createRouteItemFor(@NonNull MediaRoute2Info routeInfo) {
-        return new SystemRouteItem.Builder(routeInfo.getId())
-                .setName(String.valueOf(routeInfo.getName()))
-                .setDescription(String.valueOf(routeInfo.getDescription()))
-                .build();
+    private SystemRouteItem createRouteItemFor(@NonNull MediaRoute2Info routeInfo) {
+        SystemRouteItem.Builder builder =
+                new SystemRouteItem.Builder(routeInfo.getId())
+                        .setName(String.valueOf(routeInfo.getName()))
+                        .setDescription(String.valueOf(routeInfo.getDescription()));
+        try {
+            if (mSuitabilityStatusMethod != null) {
+                // See b/336510942 for details on why reflection is needed.
+                @SuppressLint("BanUncheckedReflection")
+                int status = (Integer) mSuitabilityStatusMethod.invoke(routeInfo);
+                builder.setSuitabilityStatus(getHumanReadableSuitabilityStatus(status));
+                // TODO: b/319645714 - Populate wasTransferInitiatedBySelf. For that we need to
+                // change the implementation of this class to use the routing controller instead
+                // of a route callback.
+            }
+        } catch (IllegalAccessException | InvocationTargetException e) {
+        }
+        return builder.build();
+    }
+
+    @NonNull
+    private String getHumanReadableSuitabilityStatus(int status) {
+        switch (status) {
+            case 0:
+                return "SUITABLE_FOR_DEFAULT_TRANSFER";
+            case 1:
+                return "SUITABLE_FOR_MANUAL_TRANSFER";
+            case 2:
+                return "NOT_SUITABLE_FOR_TRANSFER";
+            default:
+                return "UNKNOWN(" + status + ")";
+        }
     }
 }
diff --git a/samples/MediaRoutingDemo/src/main/res/layout/item_system_route.xml b/samples/MediaRoutingDemo/src/main/res/layout/item_system_route.xml
index 5d54748..2f8e533 100644
--- a/samples/MediaRoutingDemo/src/main/res/layout/item_system_route.xml
+++ b/samples/MediaRoutingDemo/src/main/res/layout/item_system_route.xml
@@ -77,6 +77,28 @@
                 android:textSize="14sp"
                 tools:text="This is a description of an amazing system route." />
 
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/route_suitability_status"
+                android:layout_marginTop="8dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/route_description"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentStart="true"
+                android:textSize="14sp"
+                tools:text="Suitability status." />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/route_transfer_initiated_by_self"
+                android:layout_marginTop="8dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/route_suitability_status"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentStart="true"
+                android:textSize="14sp"
+                tools:text="Transfer initiated by self." />
+
         </RelativeLayout>
 
     </androidx.cardview.widget.CardView>