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