1 package com.android.car.carlauncher;
2 
3 import android.car.CarProjectionManager;
4 import android.car.projection.ProjectionStatus;
5 import android.content.Context;
6 import android.content.pm.ApplicationInfo;
7 import android.content.pm.PackageManager;
8 import android.util.Log;
9 
10 import androidx.annotation.NonNull;
11 import androidx.annotation.Nullable;
12 import androidx.lifecycle.LiveData;
13 
14 import java.util.List;
15 
16 /** A {@link LiveData} of {@link ContextualInfo} on projection status. */
17 class ProjectionContextualInfoLiveData extends LiveData<ContextualInfo>
18         implements CarProjectionManager.ProjectionStatusListener {
19     private static final String TAG = "ProjectionContext";
20 
21     private final Context mContext;
22     private final CarProjectionManager mCarProjectionManager;
23 
ProjectionContextualInfoLiveData( Context context, CarProjectionManager carProjectionManager)24     ProjectionContextualInfoLiveData(
25             Context context,
26             CarProjectionManager carProjectionManager) {
27         mContext = context;
28         mCarProjectionManager = carProjectionManager;
29     }
30 
31     @Override
onActive()32     protected void onActive() {
33         super.onActive();
34         mCarProjectionManager.registerProjectionStatusListener(this);
35     }
36 
37     @Override
onInactive()38     protected void onInactive() {
39         mCarProjectionManager.unregisterProjectionStatusListener(this);
40         super.onInactive();
41     }
42 
43     @Override
onProjectionStatusChanged( int state, @Nullable String packageName, @NonNull List<ProjectionStatus> details)44     public void onProjectionStatusChanged(
45             int state, @Nullable String packageName, @NonNull List<ProjectionStatus> details) {
46         if (Log.isLoggable(TAG, Log.DEBUG)) {
47             Log.d(TAG, "onProjectionStatusChanged state=" + state + " package=" + packageName);
48         }
49         if (state == ProjectionStatus.PROJECTION_STATE_INACTIVE || packageName == null) {
50             setValue(null);
51             return;
52         }
53 
54         PackageManager pm = mContext.getPackageManager();
55         ApplicationInfo applicationInfo;
56         try {
57             applicationInfo = pm.getApplicationInfo(packageName, 0);
58         } catch (PackageManager.NameNotFoundException e) {
59             Log.e(TAG, "Could not load projection package information", e);
60             setValue(null);
61             return;
62         }
63 
64         setValue(
65                 new ContextualInfo(
66                         applicationInfo.loadIcon(pm),
67                         applicationInfo.loadLabel(pm),
68                         getStatusMessage(packageName, details),
69                         /* showClock= */ false,
70                         pm.getLaunchIntentForPackage(packageName)));
71     }
72 
73     @Nullable
getStatusMessage( String packageName, List<ProjectionStatus> details)74     private String getStatusMessage(
75             String packageName, List<ProjectionStatus> details) {
76         for (ProjectionStatus status : details) {
77             if (packageName.equals(status.getPackageName())) {
78                 return getStatusMessage(status);
79             }
80         }
81 
82         return null;
83     }
84 
85     @Nullable
getStatusMessage(ProjectionStatus status)86     private String getStatusMessage(ProjectionStatus status) {
87         // The status message is as follows:
88         // - If there is an unambiguous "best" device, the name of that device.
89         //   "Unambiguous" is defined as only one projecting device, or no projecting devices
90         //   and only one non-projecting device.
91         // - If there are multiple projecting or non-projecting devices, "N devices", where N
92         //   is the total number of projecting and non-projecting devices.
93         // - If there are no devices at all, no message. This should not happen if projection
94         //   apps are behaving properly, but may happen in the event of a projection app bug.
95         String projectingDevice = null;
96         String nonProjectingDevice = null;
97         int projectingDeviceCount = 0;
98         int nonProjectingDeviceCount = 0;
99         for (ProjectionStatus.MobileDevice device : status.getConnectedMobileDevices()) {
100             if (device.isProjecting()) {
101                 projectingDevice = device.getName();
102                 projectingDeviceCount++;
103             } else {
104                 nonProjectingDevice = device.getName();
105                 nonProjectingDeviceCount++;
106             }
107         }
108 
109         if (projectingDeviceCount == 1) {
110             return projectingDevice;
111         } else if (projectingDeviceCount == 0 && nonProjectingDeviceCount == 1) {
112             return nonProjectingDevice;
113         }
114 
115         int totalDeviceCount = projectingDeviceCount + nonProjectingDeviceCount;
116         if (totalDeviceCount > 0) {
117             return mContext.getResources().getQuantityString(
118                     R.plurals.projection_devices, totalDeviceCount, totalDeviceCount);
119         } else {
120             return null;
121         }
122     }
123 }
124