1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.car.projection;
18 
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.SystemApi;
24 import android.os.Bundle;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 
28 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
29 import com.android.car.internal.util.IntArray;
30 
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.List;
36 
37 /**
38  * This class encapsulates information about projection status and connected mobile devices.
39  *
40  * <p>Since the number of connected devices expected to be small we include information about
41  * connected devices in every status update.
42  *
43  * @hide
44  */
45 @SystemApi
46 public final class ProjectionStatus implements Parcelable {
47     /** This state indicates that projection is not actively running and no compatible mobile
48      * devices available. */
49     public static final int PROJECTION_STATE_INACTIVE = 0;
50 
51     /** At least one phone connected and ready to project. */
52     public static final int PROJECTION_STATE_READY_TO_PROJECT = 1;
53 
54     /** Projecting in the foreground */
55     public static final int PROJECTION_STATE_ACTIVE_FOREGROUND = 2;
56 
57     /** Projection is running in the background */
58     public static final int PROJECTION_STATE_ACTIVE_BACKGROUND = 3;
59 
60     /** This state indicates that at least one phone is connected and attempting to start
61      * the projection. If one mobile device is already attempting to start the projection,
62      * it should not be overridden by starting other projection technologies on top of it.
63      */
64     public static final int PROJECTION_STATE_ATTEMPTING = 4;
65 
66     /** This state indicates that at least one phone is connected and in the process of finishing
67      * the projection. If one mobile device is already finishing the projection,
68      * another mobile device should not start other projection technologies on top of it.
69      */
70     public static final int PROJECTION_STATE_FINISHING = 5;
71 
72     private static final int PROJECTION_STATE_MAX = PROJECTION_STATE_FINISHING;
73 
74     /** This status is used when projection is not actively running */
75     public static final int PROJECTION_TRANSPORT_NONE = 0;
76 
77     /** This status is used when projection is not actively running */
78     public static final int PROJECTION_TRANSPORT_USB = 1;
79 
80     /** This status is used when projection is not actively running */
81     public static final int PROJECTION_TRANSPORT_WIFI = 2;
82 
83     private static final int PROJECTION_TRANSPORT_MAX = PROJECTION_TRANSPORT_WIFI;
84 
85     /** @hide */
86     @IntDef(value = {
87             PROJECTION_TRANSPORT_NONE,
88             PROJECTION_TRANSPORT_USB,
89             PROJECTION_TRANSPORT_WIFI,
90     })
91     @Retention(RetentionPolicy.SOURCE)
92     public @interface ProjectionTransport {}
93 
94     /** @hide */
95     @IntDef(value = {
96             PROJECTION_STATE_INACTIVE,
97             PROJECTION_STATE_READY_TO_PROJECT,
98             PROJECTION_STATE_ACTIVE_FOREGROUND,
99             PROJECTION_STATE_ACTIVE_BACKGROUND,
100             PROJECTION_STATE_ATTEMPTING,
101             PROJECTION_STATE_FINISHING,
102     })
103     @Retention(RetentionPolicy.SOURCE)
104     public @interface ProjectionState {}
105 
106     private final String mPackageName;
107     private final int mState;
108     private final int mTransport;
109     private final List<MobileDevice> mConnectedMobileDevices;
110     private final Bundle mExtras;
111 
112     /** Creator for this class. Required to have in parcelable implementations. */
113     public static final Creator<ProjectionStatus> CREATOR = new Creator<ProjectionStatus>() {
114         @Override
115         public ProjectionStatus createFromParcel(Parcel source) {
116             return new ProjectionStatus(source);
117         }
118 
119         @Override
120         public ProjectionStatus[] newArray(int size) {
121             return new ProjectionStatus[size];
122         }
123     };
124 
ProjectionStatus(Builder builder)125     private ProjectionStatus(Builder builder) {
126         mPackageName = builder.mPackageName;
127         mState = builder.mState;
128         mTransport = builder.mTransport;
129         mConnectedMobileDevices = new ArrayList<>(builder.mMobileDevices);
130         mExtras = builder.mExtras == null ? null : new Bundle(builder.mExtras);
131     }
132 
ProjectionStatus(Parcel source)133     private ProjectionStatus(Parcel source) {
134         mPackageName = source.readString();
135         mState = source.readInt();
136         mTransport = source.readInt();
137         mExtras = source.readBundle(getClass().getClassLoader());
138         mConnectedMobileDevices = source.createTypedArrayList(MobileDevice.CREATOR);
139     }
140 
141     /** Parcelable implementation */
142     @Override
143     @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
describeContents()144     public int describeContents() {
145         return 0;
146     }
147 
148     @Override
writeToParcel(Parcel dest, int flags)149     public void writeToParcel(Parcel dest, int flags) {
150         dest.writeString(mPackageName);
151         dest.writeInt(mState);
152         dest.writeInt(mTransport);
153         dest.writeBundle(mExtras);
154         dest.writeTypedList(mConnectedMobileDevices);
155     }
156 
157     /** Returns projection state which could be one of the constants starting with
158      * {@code #PROJECTION_STATE_}.
159      */
getState()160     public @ProjectionState int getState() {
161         return mState;
162     }
163 
164     /** Returns package name of the projection receiver app. */
getPackageName()165     public @NonNull String getPackageName() {
166         return mPackageName;
167     }
168 
169     /** Returns extra information provided by projection receiver app */
getExtras()170     public @NonNull Bundle getExtras() {
171         return mExtras == null ? new Bundle() : new Bundle(mExtras);
172     }
173 
174     /** Returns true if currently projecting either in the foreground or in the background. */
isActive()175     public boolean isActive() {
176         return mState == PROJECTION_STATE_ACTIVE_BACKGROUND
177                 || mState == PROJECTION_STATE_ACTIVE_FOREGROUND;
178     }
179 
180     /** Returns transport which is used for active projection or
181      * {@link #PROJECTION_TRANSPORT_NONE} if projection is not running.
182      */
getTransport()183     public @ProjectionTransport int getTransport() {
184         return mTransport;
185     }
186 
187     /** Returns a list of currently connected mobile devices. */
getConnectedMobileDevices()188     public @NonNull List<MobileDevice> getConnectedMobileDevices() {
189         return new ArrayList<>(mConnectedMobileDevices);
190     }
191 
192     /**
193      * Returns new {@link Builder} instance.
194      *
195      * @param packageName package name that will be associated with this status
196      * @param state current projection state, must be one of the {@code PROJECTION_STATE_*}
197      */
198     @NonNull
builder(String packageName, @ProjectionState int state)199     public static Builder builder(String packageName, @ProjectionState int state) {
200         return new Builder(packageName, state);
201     }
202 
203     /** Builder class for {@link ProjectionStatus} */
204     public static final class Builder {
205         private final int mState;
206         private final String mPackageName;
207         private int mTransport = PROJECTION_TRANSPORT_NONE;
208         private List<MobileDevice> mMobileDevices = new ArrayList<>();
209         private Bundle mExtras;
210 
Builder(String packageName, @ProjectionState int state)211         private Builder(String packageName, @ProjectionState int state) {
212             if (packageName == null) {
213                 throw new IllegalArgumentException("Package name can't be null");
214             }
215             if (state < 0 || state > PROJECTION_STATE_MAX) {
216                 throw new IllegalArgumentException("Invalid projection state: " + state);
217             }
218             mPackageName = packageName;
219             mState = state;
220         }
221 
222         /**
223          * Sets the transport which is used for currently projecting phone if any.
224          *
225          * @param transport transport of current projection, must be one of the
226          * {@code PROJECTION_TRANSPORT_*}
227          */
setProjectionTransport(@rojectionTransport int transport)228         public @NonNull Builder setProjectionTransport(@ProjectionTransport int transport) {
229             checkProjectionTransport(transport);
230             mTransport = transport;
231             return this;
232         }
233 
234         /**
235          * Add connected mobile device
236          *
237          * @param mobileDevice connected mobile device
238          * @return this builder
239          */
addMobileDevice(MobileDevice mobileDevice)240         public @NonNull Builder addMobileDevice(MobileDevice mobileDevice) {
241             mMobileDevices.add(mobileDevice);
242             return this;
243         }
244 
245         /**
246          * Add extra information.
247          *
248          * @param extras may contain an extra information that can be passed from the projection
249          * app to the projection status listeners
250          * @return this builder
251          */
setExtras(Bundle extras)252         public @NonNull Builder setExtras(Bundle extras) {
253             mExtras = extras;
254             return this;
255         }
256 
257         /** Creates {@link ProjectionStatus} object. */
build()258         public ProjectionStatus build() {
259             return new ProjectionStatus(this);
260         }
261     }
262 
checkProjectionTransport(@rojectionTransport int transport)263     private static void checkProjectionTransport(@ProjectionTransport int transport) {
264         if (transport < 0 || transport > PROJECTION_TRANSPORT_MAX) {
265             throw new IllegalArgumentException("Invalid projection transport: " + transport);
266         }
267     }
268 
269     @Override
toString()270     public String toString() {
271         return "ProjectionStatus{"
272                 + "mPackageName='" + mPackageName + '\''
273                 + ", mState=" + mState
274                 + ", mTransport=" + mTransport
275                 + ", mConnectedMobileDevices=" + mConnectedMobileDevices
276                 + (mExtras != null ? " (has extras)" : "")
277                 + '}';
278     }
279 
280     /** Class that represents information about connected mobile device. */
281     public static final class MobileDevice implements Parcelable {
282         private final int mId;
283         private final String mName;
284         private final int[] mAvailableTransports;
285         private final boolean mProjecting;
286         private final Bundle mExtras;
287 
288         /** Creator for this class. Required to have in parcelable implementations. */
289         public static final Creator<MobileDevice> CREATOR = new Creator<MobileDevice>() {
290             @Override
291             public MobileDevice createFromParcel(Parcel source) {
292                 return new MobileDevice(source);
293             }
294 
295             @Override
296             public MobileDevice[] newArray(int size) {
297                 return new MobileDevice[size];
298             }
299         };
300 
MobileDevice(Builder builder)301         private MobileDevice(Builder builder) {
302             mId = builder.mId;
303             mName = builder.mName;
304             mAvailableTransports = builder.mAvailableTransports.toArray();
305             mProjecting = builder.mProjecting;
306             mExtras = builder.mExtras == null ? null : new Bundle(builder.mExtras);
307         }
308 
MobileDevice(Parcel source)309         private MobileDevice(Parcel source) {
310             mId = source.readInt();
311             mName = source.readString();
312             mAvailableTransports = source.createIntArray();
313             mProjecting = source.readBoolean();
314             mExtras = source.readBundle(getClass().getClassLoader());
315         }
316 
317         @Override
writeToParcel(Parcel dest, int flags)318         public void writeToParcel(Parcel dest, int flags) {
319             dest.writeInt(mId);
320             dest.writeString(mName);
321             dest.writeIntArray(mAvailableTransports);
322             dest.writeBoolean(mProjecting);
323             dest.writeBundle(mExtras);
324         }
325 
326         /** Returns the device id which uniquely identifies the mobile device within projection  */
getId()327         public int getId() {
328             return mId;
329         }
330 
331         /** Returns the name of the device */
getName()332         public @NonNull String getName() {
333             return mName;
334         }
335 
336         /** Returns a list of available projection transports. See {@code PROJECTION_TRANSPORT_*}
337          * for possible values. */
getAvailableTransports()338         public @NonNull List<Integer> getAvailableTransports() {
339             List<Integer> transports = new ArrayList<>(mAvailableTransports.length);
340             for (int transport : mAvailableTransports) {
341                 transports.add(transport);
342             }
343             return transports;
344         }
345 
346         /** Indicates whether this mobile device is currently projecting */
isProjecting()347         public boolean isProjecting() {
348             return mProjecting;
349         }
350 
351         /** Returns extra information for mobile device */
getExtras()352         public @NonNull Bundle getExtras() {
353             return mExtras == null ? new Bundle() : new Bundle(mExtras);
354         }
355 
356         /** Parcelable implementation */
357         @Override
describeContents()358         public int describeContents() {
359             return 0;
360         }
361 
362         /**
363          * Creates new instance of {@link Builder}
364          *
365          * @param id uniquely identifies the device
366          * @param name name of the connected device
367          * @return the instance of {@link Builder}
368          */
builder(int id, String name)369         public static @NonNull Builder builder(int id, String name) {
370             return new Builder(id, name);
371         }
372 
373         @Override
toString()374         public String toString() {
375             return "MobileDevice{"
376                     + "mId=" + mId
377                     + ", mName='" + mName + '\''
378                     + ", mAvailableTransports=" + Arrays.toString(mAvailableTransports)
379                     + ", mProjecting=" + mProjecting
380                     + (mExtras != null ? ", (has extras)" : "")
381                     + '}';
382         }
383 
384         /**
385          * Builder class for {@link MobileDevice}
386          */
387         public static final class Builder {
388             private int mId;
389             private String mName;
390             private IntArray mAvailableTransports = new IntArray();
391             private boolean mProjecting;
392             private Bundle mExtras;
393 
Builder(int id, String name)394             private Builder(int id, String name) {
395                 mId = id;
396                 if (name == null) {
397                     throw new IllegalArgumentException("Name of the device can't be null");
398                 }
399                 mName = name;
400             }
401 
402             /**
403              * Add supported transport
404              *
405              * @param transport supported transport by given device, must be one of the
406              * {@code PROJECTION_TRANSPORT_*}
407              * @return this builder
408              */
addTransport(@rojectionTransport int transport)409             public @NonNull Builder addTransport(@ProjectionTransport int transport) {
410                 checkProjectionTransport(transport);
411                 mAvailableTransports.add(transport);
412                 return this;
413             }
414 
415             /**
416              * Indicate whether the mobile device currently projecting or not.
417              *
418              * @param projecting {@code True} if this mobile device currently projecting
419              * @return this builder
420              */
setProjecting(boolean projecting)421             public @NonNull Builder setProjecting(boolean projecting) {
422                 mProjecting = projecting;
423                 return this;
424             }
425 
426             /**
427              * Add extra information for mobile device
428              *
429              * @param extras provides an arbitrary extra information about this mobile device
430              * @return this builder
431              */
setExtras(Bundle extras)432             public @NonNull Builder setExtras(Bundle extras) {
433                 mExtras = extras;
434                 return this;
435             }
436 
437             /** Creates new instance of {@link MobileDevice} */
build()438             public @NonNull MobileDevice build() {
439                 return new MobileDevice(this);
440             }
441         }
442     }
443 }
444