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 toString()278 public String toString() { 279 return "ProjectionStatus{" 280 + "mPackageName='" + mPackageName + '\'' 281 + ", mState=" + mState 282 + ", mTransport=" + mTransport 283 + ", mConnectedMobileDevices=" + mConnectedMobileDevices 284 + (mExtras != null ? " (has extras)" : "") 285 + '}'; 286 } 287 288 /** Class that represents information about connected mobile device. */ 289 public static final class MobileDevice implements Parcelable { 290 private final int mId; 291 private final String mName; 292 private final int[] mAvailableTransports; 293 private final boolean mProjecting; 294 private final Bundle mExtras; 295 296 /** Creator for this class. Required to have in parcelable implementations. */ 297 @AddedInOrBefore(majorVersion = 33) 298 public static final Creator<MobileDevice> CREATOR = new Creator<MobileDevice>() { 299 @Override 300 public MobileDevice createFromParcel(Parcel source) { 301 return new MobileDevice(source); 302 } 303 304 @Override 305 public MobileDevice[] newArray(int size) { 306 return new MobileDevice[size]; 307 } 308 }; 309 MobileDevice(Builder builder)310 private MobileDevice(Builder builder) { 311 mId = builder.mId; 312 mName = builder.mName; 313 mAvailableTransports = builder.mAvailableTransports.toArray(); 314 mProjecting = builder.mProjecting; 315 mExtras = builder.mExtras == null ? null : new Bundle(builder.mExtras); 316 } 317 MobileDevice(Parcel source)318 private MobileDevice(Parcel source) { 319 mId = source.readInt(); 320 mName = source.readString(); 321 mAvailableTransports = source.createIntArray(); 322 mProjecting = source.readBoolean(); 323 mExtras = source.readBundle(getClass().getClassLoader()); 324 } 325 326 @Override 327 @AddedInOrBefore(majorVersion = 33) writeToParcel(Parcel dest, int flags)328 public void writeToParcel(Parcel dest, int flags) { 329 dest.writeInt(mId); 330 dest.writeString(mName); 331 dest.writeIntArray(mAvailableTransports); 332 dest.writeBoolean(mProjecting); 333 dest.writeBundle(mExtras); 334 } 335 336 /** Returns the device id which uniquely identifies the mobile device within projection */ 337 @AddedInOrBefore(majorVersion = 33) getId()338 public int getId() { 339 return mId; 340 } 341 342 /** Returns the name of the device */ 343 @AddedInOrBefore(majorVersion = 33) getName()344 public @NonNull String getName() { 345 return mName; 346 } 347 348 /** Returns a list of available projection transports. See {@code PROJECTION_TRANSPORT_*} 349 * for possible values. */ 350 @AddedInOrBefore(majorVersion = 33) getAvailableTransports()351 public @NonNull List<Integer> getAvailableTransports() { 352 List<Integer> transports = new ArrayList<>(mAvailableTransports.length); 353 for (int transport : mAvailableTransports) { 354 transports.add(transport); 355 } 356 return transports; 357 } 358 359 /** Indicates whether this mobile device is currently projecting */ 360 @AddedInOrBefore(majorVersion = 33) isProjecting()361 public boolean isProjecting() { 362 return mProjecting; 363 } 364 365 /** Returns extra information for mobile device */ 366 @AddedInOrBefore(majorVersion = 33) getExtras()367 public @NonNull Bundle getExtras() { 368 return mExtras == null ? new Bundle() : new Bundle(mExtras); 369 } 370 371 /** Parcelable implementation */ 372 @Override 373 @AddedInOrBefore(majorVersion = 33) describeContents()374 public int describeContents() { 375 return 0; 376 } 377 378 /** 379 * Creates new instance of {@link Builder} 380 * 381 * @param id uniquely identifies the device 382 * @param name name of the connected device 383 * @return the instance of {@link Builder} 384 */ 385 @AddedInOrBefore(majorVersion = 33) builder(int id, String name)386 public static @NonNull Builder builder(int id, String name) { 387 return new Builder(id, name); 388 } 389 390 @Override toString()391 public String toString() { 392 return "MobileDevice{" 393 + "mId=" + mId 394 + ", mName='" + mName + '\'' 395 + ", mAvailableTransports=" + Arrays.toString(mAvailableTransports) 396 + ", mProjecting=" + mProjecting 397 + (mExtras != null ? ", (has extras)" : "") 398 + '}'; 399 } 400 401 /** 402 * Builder class for {@link MobileDevice} 403 */ 404 public static final class Builder { 405 private int mId; 406 private String mName; 407 private IntArray mAvailableTransports = new IntArray(); 408 private boolean mProjecting; 409 private Bundle mExtras; 410 Builder(int id, String name)411 private Builder(int id, String name) { 412 mId = id; 413 if (name == null) { 414 throw new IllegalArgumentException("Name of the device can't be null"); 415 } 416 mName = name; 417 } 418 419 /** 420 * Add supported transport 421 * 422 * @param transport supported transport by given device, must be one of the 423 * {@code PROJECTION_TRANSPORT_*} 424 * @return this builder 425 */ 426 @AddedInOrBefore(majorVersion = 33) addTransport(@rojectionTransport int transport)427 public @NonNull Builder addTransport(@ProjectionTransport int transport) { 428 checkProjectionTransport(transport); 429 mAvailableTransports.add(transport); 430 return this; 431 } 432 433 /** 434 * Indicate whether the mobile device currently projecting or not. 435 * 436 * @param projecting {@code True} if this mobile device currently projecting 437 * @return this builder 438 */ 439 @AddedInOrBefore(majorVersion = 33) setProjecting(boolean projecting)440 public @NonNull Builder setProjecting(boolean projecting) { 441 mProjecting = projecting; 442 return this; 443 } 444 445 /** 446 * Add extra information for mobile device 447 * 448 * @param extras provides an arbitrary extra information about this mobile device 449 * @return this builder 450 */ 451 @AddedInOrBefore(majorVersion = 33) setExtras(Bundle extras)452 public @NonNull Builder setExtras(Bundle extras) { 453 mExtras = extras; 454 return this; 455 } 456 457 /** Creates new instance of {@link MobileDevice} */ 458 @AddedInOrBefore(majorVersion = 33) build()459 public @NonNull MobileDevice build() { 460 return new MobileDevice(this); 461 } 462 } 463 } 464 } 465