/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.car.projection; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.IntArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * This class encapsulates information about projection status and connected mobile devices. * *

Since the number of connected devices expected to be small we include information about * connected devices in every status update. * * @hide */ @SystemApi public final class ProjectionStatus implements Parcelable { /** This state indicates that projection is not actively running and no compatible mobile * devices available. */ public static final int PROJECTION_STATE_INACTIVE = 0; /** At least one phone connected and ready to project. */ public static final int PROJECTION_STATE_READY_TO_PROJECT = 1; /** Projecting in the foreground */ public static final int PROJECTION_STATE_ACTIVE_FOREGROUND = 2; /** Projection is running in the background */ public static final int PROJECTION_STATE_ACTIVE_BACKGROUND = 3; private static final int PROJECTION_STATE_MAX = PROJECTION_STATE_ACTIVE_BACKGROUND; /** This status is used when projection is not actively running */ public static final int PROJECTION_TRANSPORT_NONE = 0; /** This status is used when projection is not actively running */ public static final int PROJECTION_TRANSPORT_USB = 1; /** This status is used when projection is not actively running */ public static final int PROJECTION_TRANSPORT_WIFI = 2; private static final int PROJECTION_TRANSPORT_MAX = PROJECTION_TRANSPORT_WIFI; /** @hide */ @IntDef(value = { PROJECTION_TRANSPORT_NONE, PROJECTION_TRANSPORT_USB, PROJECTION_TRANSPORT_WIFI, }) @Retention(RetentionPolicy.SOURCE) public @interface ProjectionTransport {} /** @hide */ @IntDef(value = { PROJECTION_STATE_INACTIVE, PROJECTION_STATE_READY_TO_PROJECT, PROJECTION_STATE_ACTIVE_FOREGROUND, PROJECTION_STATE_ACTIVE_BACKGROUND, }) @Retention(RetentionPolicy.SOURCE) public @interface ProjectionState {} private final String mPackageName; private final int mState; private final int mTransport; private final List mConnectedMobileDevices; private final Bundle mExtras; /** Creator for this class. Required to have in parcelable implementations. */ public static final Creator CREATOR = new Creator() { @Override public ProjectionStatus createFromParcel(Parcel source) { return new ProjectionStatus(source); } @Override public ProjectionStatus[] newArray(int size) { return new ProjectionStatus[size]; } }; private ProjectionStatus(Builder builder) { mPackageName = builder.mPackageName; mState = builder.mState; mTransport = builder.mTransport; mConnectedMobileDevices = new ArrayList<>(builder.mMobileDevices); mExtras = builder.mExtras == null ? null : new Bundle(builder.mExtras); } private ProjectionStatus(Parcel source) { mPackageName = source.readString(); mState = source.readInt(); mTransport = source.readInt(); mExtras = source.readBundle(getClass().getClassLoader()); mConnectedMobileDevices = source.createTypedArrayList(MobileDevice.CREATOR); } /** Parcelable implementation */ @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mPackageName); dest.writeInt(mState); dest.writeInt(mTransport); dest.writeBundle(mExtras); dest.writeTypedList(mConnectedMobileDevices); } /** Returns projection state which could be one of the constants starting with * {@code #PROJECTION_STATE_}. */ public @ProjectionState int getState() { return mState; } /** Returns package name of the projection receiver app. */ public @NonNull String getPackageName() { return mPackageName; } /** Returns extra information provided by projection receiver app */ public @NonNull Bundle getExtras() { return mExtras == null ? new Bundle() : new Bundle(mExtras); } /** Returns true if currently projecting either in the foreground or in the background. */ public boolean isActive() { return mState == PROJECTION_STATE_ACTIVE_BACKGROUND || mState == PROJECTION_STATE_ACTIVE_FOREGROUND; } /** Returns transport which is used for active projection or * {@link #PROJECTION_TRANSPORT_NONE} if projection is not running. */ public @ProjectionTransport int getTransport() { return mTransport; } /** Returns a list of currently connected mobile devices. */ public @NonNull List getConnectedMobileDevices() { return new ArrayList<>(mConnectedMobileDevices); } /** * Returns new {@link Builder} instance. * * @param packageName package name that will be associated with this status * @param state current projection state, must be one of the {@code PROJECTION_STATE_*} */ @NonNull public static Builder builder(String packageName, @ProjectionState int state) { return new Builder(packageName, state); } /** Builder class for {@link ProjectionStatus} */ public static final class Builder { private final int mState; private final String mPackageName; private int mTransport = PROJECTION_TRANSPORT_NONE; private List mMobileDevices = new ArrayList<>(); private Bundle mExtras; private Builder(String packageName, @ProjectionState int state) { if (packageName == null) { throw new IllegalArgumentException("Package name can't be null"); } if (state < 0 || state > PROJECTION_STATE_MAX) { throw new IllegalArgumentException("Invalid projection state: " + state); } mPackageName = packageName; mState = state; } /** * Sets the transport which is used for currently projecting phone if any. * * @param transport transport of current projection, must be one of the * {@code PROJECTION_TRANSPORT_*} */ public @NonNull Builder setProjectionTransport(@ProjectionTransport int transport) { checkProjectionTransport(transport); mTransport = transport; return this; } /** * Add connected mobile device * * @param mobileDevice connected mobile device * @return this builder */ public @NonNull Builder addMobileDevice(MobileDevice mobileDevice) { mMobileDevices.add(mobileDevice); return this; } /** * Add extra information. * * @param extras may contain an extra information that can be passed from the projection * app to the projection status listeners * @return this builder */ public @NonNull Builder setExtras(Bundle extras) { mExtras = extras; return this; } /** Creates {@link ProjectionStatus} object. */ public ProjectionStatus build() { return new ProjectionStatus(this); } } private static void checkProjectionTransport(@ProjectionTransport int transport) { if (transport < 0 || transport > PROJECTION_TRANSPORT_MAX) { throw new IllegalArgumentException("Invalid projection transport: " + transport); } } @Override public String toString() { return "ProjectionStatus{" + "mPackageName='" + mPackageName + '\'' + ", mState=" + mState + ", mTransport=" + mTransport + ", mConnectedMobileDevices=" + mConnectedMobileDevices + (mExtras != null ? " (has extras)" : "") + '}'; } /** Class that represents information about connected mobile device. */ public static final class MobileDevice implements Parcelable { private final int mId; private final String mName; private final int[] mAvailableTransports; private final boolean mProjecting; private final Bundle mExtras; /** Creator for this class. Required to have in parcelable implementations. */ public static final Creator CREATOR = new Creator() { @Override public MobileDevice createFromParcel(Parcel source) { return new MobileDevice(source); } @Override public MobileDevice[] newArray(int size) { return new MobileDevice[size]; } }; private MobileDevice(Builder builder) { mId = builder.mId; mName = builder.mName; mAvailableTransports = builder.mAvailableTransports.toArray(); mProjecting = builder.mProjecting; mExtras = builder.mExtras == null ? null : new Bundle(builder.mExtras); } private MobileDevice(Parcel source) { mId = source.readInt(); mName = source.readString(); mAvailableTransports = source.createIntArray(); mProjecting = source.readBoolean(); mExtras = source.readBundle(getClass().getClassLoader()); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mId); dest.writeString(mName); dest.writeIntArray(mAvailableTransports); dest.writeBoolean(mProjecting); dest.writeBundle(mExtras); } /** Returns the device id which uniquely identifies the mobile device within projection */ public int getId() { return mId; } /** Returns the name of the device */ public @NonNull String getName() { return mName; } /** Returns a list of available projection transports. See {@code PROJECTION_TRANSPORT_*} * for possible values. */ public @NonNull List getAvailableTransports() { List transports = new ArrayList<>(mAvailableTransports.length); for (int transport : mAvailableTransports) { transports.add(transport); } return transports; } /** Indicates whether this mobile device is currently projecting */ public boolean isProjecting() { return mProjecting; } /** Returns extra information for mobile device */ public @NonNull Bundle getExtras() { return mExtras == null ? new Bundle() : new Bundle(mExtras); } /** Parcelable implementation */ @Override public int describeContents() { return 0; } /** * Creates new instance of {@link Builder} * * @param id uniquely identifies the device * @param name name of the connected device * @return the instance of {@link Builder} */ public static @NonNull Builder builder(int id, String name) { return new Builder(id, name); } @Override public String toString() { return "MobileDevice{" + "mId=" + mId + ", mName='" + mName + '\'' + ", mAvailableTransports=" + Arrays.toString(mAvailableTransports) + ", mProjecting=" + mProjecting + (mExtras != null ? ", (has extras)" : "") + '}'; } /** * Builder class for {@link MobileDevice} */ public static final class Builder { private int mId; private String mName; private IntArray mAvailableTransports = new IntArray(); private boolean mProjecting; private Bundle mExtras; private Builder(int id, String name) { mId = id; if (name == null) { throw new IllegalArgumentException("Name of the device can't be null"); } mName = name; } /** * Add supported transport * * @param transport supported transport by given device, must be one of the * {@code PROJECTION_TRANSPORT_*} * @return this builder */ public @NonNull Builder addTransport(@ProjectionTransport int transport) { checkProjectionTransport(transport); mAvailableTransports.add(transport); return this; } /** * Indicate whether the mobile device currently projecting or not. * * @param projecting {@code True} if this mobile device currently projecting * @return this builder */ public @NonNull Builder setProjecting(boolean projecting) { mProjecting = projecting; return this; } /** * Add extra information for mobile device * * @param extras provides an arbitrary extra information about this mobile device * @return this builder */ public @NonNull Builder setExtras(Bundle extras) { mExtras = extras; return this; } /** Creates new instance of {@link MobileDevice} */ public @NonNull MobileDevice build() { return new MobileDevice(this); } } } }