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