Added a fragment to the Kitchen Sink application to help debug
projection status information.

The added fragment displays current projection status information, such
as the projecting package, the connected devices, available transports,
etc. This change also fixes some logic in CarProjectionService which was
not always updating projection status when necessary.

Bug: 133351166
Bug: 133367487

Test: Connect a MD with android auto to an embedded HU running
phantoscope. The values in the kitchen sink app should populate.

Change-Id: I9cc413625ff95c8d750bd6e375493abc7f4f3017
diff --git a/service/src/com/android/car/CarProjectionService.java b/service/src/com/android/car/CarProjectionService.java
index 33ed8f6..144804e 100644
--- a/service/src/com/android/car/CarProjectionService.java
+++ b/service/src/com/android/car/CarProjectionService.java
@@ -17,6 +17,7 @@
 
 import static android.car.CarProjectionManager.ProjectionAccessPointCallback.ERROR_GENERIC;
 import static android.car.projection.ProjectionStatus.PROJECTION_STATE_INACTIVE;
+import static android.car.projection.ProjectionStatus.PROJECTION_STATE_READY_TO_PROJECT;
 import static android.net.wifi.WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_FAILURE_REASON;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
@@ -410,7 +411,15 @@
             ProjectionReceiverClient client = getOrCreateProjectionReceiverClientLocked(token);
             client.mProjectionStatus = status;
 
-            if (status.isActive() || TextUtils.equals(packageName, mCurrentProjectionPackage)) {
+            // If the projection package that's reporting its projection state is the currently
+            // active projection package, update the state. If it is a different package, update the
+            // current projection state if the new package is reporting that it is projecting or if
+            // it is reporting that it's ready to project, and the current package has an inactive
+            // projection state.
+            if (status.isActive()
+                    || (status.getState() == PROJECTION_STATE_READY_TO_PROJECT
+                            && mCurrentProjectionState == PROJECTION_STATE_INACTIVE)
+                    || TextUtils.equals(packageName, mCurrentProjectionPackage)) {
                 mCurrentProjectionState = status.getState();
                 mCurrentProjectionPackage = packageName;
             }
diff --git a/tests/EmbeddedKitchenSinkApp/Android.mk b/tests/EmbeddedKitchenSinkApp/Android.mk
index 65de56e..386044c 100644
--- a/tests/EmbeddedKitchenSinkApp/Android.mk
+++ b/tests/EmbeddedKitchenSinkApp/Android.mk
@@ -49,6 +49,7 @@
     android.hardware.automotive.vehicle-V2.0-java \
     vehicle-hal-support-lib \
     com.android.car.keventreader-client \
+    guava \
     kitchensink-gson
 
 LOCAL_JAVA_LIBRARIES += android.car
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/projection_status.xml b/tests/EmbeddedKitchenSinkApp/res/layout/projection_status.xml
new file mode 100644
index 0000000..65ccbe8
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/projection_status.xml
@@ -0,0 +1,55 @@
+<?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:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <LinearLayout android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:orientation="horizontal">
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="Current Projection State: "/>
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="Inactive"
+                  android:id="@+id/current_projection_status"/>
+    </LinearLayout>
+    <LinearLayout android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:orientation="horizontal">
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="Current Projection Package: "/>
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="Unknown"
+                  android:id="@+id/current_projection_package"/>
+    </LinearLayout>
+    <LinearLayout android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:orientation="vertical">
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="Current Projection Details: "/>
+        <LinearLayout android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:orientation="vertical"
+                      android:id="@+id/current_projection_details"/>
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/projection_status_details.xml b/tests/EmbeddedKitchenSinkApp/res/layout/projection_status_details.xml
new file mode 100644
index 0000000..ec06152
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/projection_status_details.xml
@@ -0,0 +1,61 @@
+<?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:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="Package: "/>
+        <TextView android:layout_height="wrap_content"
+                  android:layout_width="wrap_content"
+                  android:id="@+id/projection_detail_package"/>
+    </LinearLayout>
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="Projection State: "/>
+        <TextView android:layout_height="wrap_content"
+                  android:layout_width="wrap_content"
+                  android:id="@+id/projection_detail_state"/>
+    </LinearLayout>
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="Transport: "/>
+        <TextView android:layout_height="wrap_content"
+                  android:layout_width="wrap_content"
+                  android:id="@+id/projection_detail_transport"/>
+    </LinearLayout>
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="Devices: "/>
+        <LinearLayout android:layout_height="wrap_content"
+                      android:layout_width="wrap_content"
+                      android:orientation="vertical"
+                      android:id="@+id/projection_detail_devices"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/projection_status_device.xml b/tests/EmbeddedKitchenSinkApp/res/layout/projection_status_device.xml
new file mode 100644
index 0000000..ae4d7a9
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/projection_status_device.xml
@@ -0,0 +1,61 @@
+<?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:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="ID: "/>
+        <TextView android:layout_height="wrap_content"
+                  android:layout_width="wrap_content"
+                  android:id="@+id/projection_device_id"/>
+    </LinearLayout>
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="Name: "/>
+        <TextView android:layout_height="wrap_content"
+                  android:layout_width="wrap_content"
+                  android:id="@+id/projection_device_name"/>
+    </LinearLayout>
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="Transports: "/>
+        <LinearLayout android:layout_height="wrap_content"
+                      android:layout_width="wrap_content"
+                      android:orientation="vertical"
+                      android:id="@+id/projection_device_transports"/>
+    </LinearLayout>
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:text="Projecting: "/>
+        <TextView android:layout_height="wrap_content"
+                  android:layout_width="wrap_content"
+                  android:id="@+id/projection_device_projecting"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index a80c3b4..70949a9 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -18,6 +18,7 @@
 
 import android.car.Car;
 import android.car.CarAppFocusManager;
+import android.car.CarProjectionManager;
 import android.car.hardware.CarSensorManager;
 import android.car.hardware.hvac.CarHvacManager;
 import android.car.hardware.power.CarPowerManager;
@@ -60,6 +61,7 @@
 import com.google.android.car.kitchensink.notification.NotificationFragment;
 import com.google.android.car.kitchensink.orientation.OrientationTestFragment;
 import com.google.android.car.kitchensink.power.PowerTestFragment;
+import com.google.android.car.kitchensink.projection.ProjectionFragment;
 import com.google.android.car.kitchensink.property.PropertyTestFragment;
 import com.google.android.car.kitchensink.sensor.SensorsTestFragment;
 import com.google.android.car.kitchensink.storagelifetime.StorageLifetimeFragment;
@@ -172,6 +174,7 @@
             new FragmentMenuEntry("notification", NotificationFragment.class),
             new FragmentMenuEntry("orientation test", OrientationTestFragment.class),
             new FragmentMenuEntry("power test", PowerTestFragment.class),
+            new FragmentMenuEntry("projection", ProjectionFragment.class),
             new FragmentMenuEntry("property test", PropertyTestFragment.class),
             new FragmentMenuEntry("sensors", SensorsTestFragment.class),
             new FragmentMenuEntry("storage lifetime", StorageLifetimeFragment.class),
@@ -189,6 +192,7 @@
     private CarPropertyManager mPropertyManager;
     private CarSensorManager mSensorManager;
     private CarAppFocusManager mCarAppFocusManager;
+    private CarProjectionManager mCarProjectionManager;
     private Object mPropertyManagerReady = new Object();
 
     public CarHvacManager getHvacManager() {
@@ -207,6 +211,10 @@
         return mSensorManager;
     }
 
+    public CarProjectionManager getProjectionManager() {
+        return mCarProjectionManager;
+    }
+
     /* Open any tab directly:
      * adb shell am force-stop com.google.android.car.kitchensink
      * adb shell am start -n com.google.android.car.kitchensink/.KitchenSinkActivity \
@@ -322,6 +330,8 @@
                         android.car.Car.SENSOR_SERVICE);
                 mCarAppFocusManager =
                         (CarAppFocusManager) mCarApi.getCarManager(Car.APP_FOCUS_SERVICE);
+                mCarProjectionManager =
+                        (CarProjectionManager) mCarApi.getCarManager(Car.PROJECTION_SERVICE);
                 mPropertyManagerReady.notifyAll();
             }
         }
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/projection/ProjectionFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/projection/ProjectionFragment.java
new file mode 100644
index 0000000..66fc1e2
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/projection/ProjectionFragment.java
@@ -0,0 +1,151 @@
+/*
+ * 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.kitchensink.projection;
+
+import android.car.CarProjectionManager;
+import android.car.CarProjectionManager.ProjectionStatusListener;
+import android.car.projection.ProjectionStatus;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+
+import com.google.android.car.kitchensink.KitchenSinkActivity;
+import com.google.android.car.kitchensink.R;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.List;
+
+/**
+ * Reports information about the current projection status.
+ */
+public class ProjectionFragment extends Fragment {
+    private KitchenSinkActivity mActivity;
+    private CarProjectionManager mCarProjectionManager;
+
+    private TextView mCurrentProjectionStatus;
+    private TextView mCurrentProjectionPackage;
+    private LinearLayout mCurrentProjectionDetails;
+
+    private static final ImmutableMap<Integer, String> STATE_TO_STRING = ImmutableMap.of(
+            ProjectionStatus.PROJECTION_STATE_INACTIVE, "Inactive",
+            ProjectionStatus.PROJECTION_STATE_READY_TO_PROJECT, "Ready to project",
+            ProjectionStatus.PROJECTION_STATE_ACTIVE_FOREGROUND, "Foreground",
+            ProjectionStatus.PROJECTION_STATE_ACTIVE_BACKGROUND, "Background");
+
+    private static final ImmutableMap<Integer, String> TRANSPORT_TO_STRING = ImmutableMap.of(
+            ProjectionStatus.PROJECTION_TRANSPORT_NONE, "None",
+            ProjectionStatus.PROJECTION_TRANSPORT_USB, "USB",
+            ProjectionStatus.PROJECTION_TRANSPORT_WIFI, "WiFi");
+
+    private class KitchenSinkProjectionStatusListener implements ProjectionStatusListener {
+        @Override
+        public void onProjectionStatusChanged(
+                int state,
+                String packageName,
+                List<ProjectionStatus> details) {
+            mCurrentProjectionStatus.setText(STATE_TO_STRING.get(state));
+            mCurrentProjectionPackage.setText(packageName);
+            mCurrentProjectionDetails.removeAllViews();
+            for (ProjectionStatus detail : details) {
+                LinearLayout detailLayout =
+                        (LinearLayout)
+                                getLayoutInflater()
+                                        .inflate(R.layout.projection_status_details, null);
+
+                TextView detailPackage = detailLayout.findViewById(R.id.projection_detail_package);
+                detailPackage.setText(detail.getPackageName());
+
+                TextView detailState = detailLayout.findViewById(R.id.projection_detail_state);
+                detailState.setText(STATE_TO_STRING.get(detail.getState()));
+
+                TextView detailTransport =
+                        detailLayout.findViewById(R.id.projection_detail_transport);
+                detailTransport.setText(TRANSPORT_TO_STRING.get(detail.getTransport()));
+
+                for (ProjectionStatus.MobileDevice device : detail.getConnectedMobileDevices()) {
+                    LinearLayout deviceLayout =
+                            (LinearLayout)
+                                    getLayoutInflater()
+                                            .inflate(R.layout.projection_status_device, null);
+
+                    TextView deviceId = deviceLayout.findViewById(R.id.projection_device_id);
+                    deviceId.setText(String.valueOf(device.getId()));
+
+                    TextView deviceName = deviceLayout.findViewById(R.id.projection_device_name);
+                    deviceName.setText(device.getName());
+
+                    LinearLayout deviceTransports =
+                            deviceLayout.findViewById(R.id.projection_device_transports);
+                    for (Integer transport : device.getAvailableTransports()) {
+                        TextView transportView = new TextView(mActivity);
+                        transportView.setText(TRANSPORT_TO_STRING.get(transport));
+
+                        deviceTransports.addView(transportView);
+                    }
+
+                    TextView deviceProjecting =
+                            deviceLayout.findViewById(R.id.projection_device_projecting);
+                    deviceProjecting.setText(String.valueOf(device.isProjecting()));
+
+                    detailLayout.addView(deviceLayout);
+                }
+                mCurrentProjectionDetails.addView(detailLayout);
+            }
+        }
+    }
+
+    private final KitchenSinkProjectionStatusListener mProjectionListener =
+            new KitchenSinkProjectionStatusListener();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        mActivity = (KitchenSinkActivity) getActivity();
+        mCarProjectionManager = mActivity.getProjectionManager();
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater,
+            ViewGroup container,
+            Bundle savedInstanceState) {
+        View layout = inflater.inflate(R.layout.projection_status, container, false);
+
+        mCurrentProjectionStatus = (TextView) layout.findViewById(R.id.current_projection_status);
+        mCurrentProjectionPackage = (TextView) layout.findViewById(R.id.current_projection_package);
+        mCurrentProjectionDetails =
+                (LinearLayout) layout.findViewById(R.id.current_projection_details);
+
+        return layout;
+    }
+
+    @Override
+    public void onStart() {
+        mCarProjectionManager.registerProjectionStatusListener(mProjectionListener);
+        super.onStart();
+    }
+
+    @Override
+    public void onStop() {
+        mCarProjectionManager.unregisterProjectionStatusListener(mProjectionListener);
+        super.onStop();
+    }
+}