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