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