content: /*
   * Copyright (C) 2013 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.print;
  
  import android.annotation.DrawableRes;
  import android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.TestApi;
  import android.app.PendingIntent;
  import android.content.Context;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.PackageInfo;
  import android.content.pm.PackageManager;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.graphics.drawable.Drawable;
  import android.graphics.drawable.Icon;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.service.print.PrinterInfoProto;
  import android.text.TextUtils;
  
  import com.android.internal.util.Preconditions;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  
  /**
   * This class represents the description of a printer. Instances of
   * this class are created by print services to report to the system
   * the printers they manage. The information of this class has two
   * major components, printer properties such as name, id, status,
   * description and printer capabilities which describe the various
   * print modes a printer supports such as media sizes, margins, etc.
   * <p>
   * Once {@link PrinterInfo.Builder#build() built} the objects are immutable.
   * </p>
   */
  public final class PrinterInfo implements Parcelable {
  
      /** @hide */
      @IntDef(prefix = { "STATUS_" }, value = {
              STATUS_IDLE,
              STATUS_BUSY,
              STATUS_UNAVAILABLE
      })
      @Retention(RetentionPolicy.SOURCE)
      public @interface Status {
      }
  
      /** Printer status: the printer is idle and ready to print. */
      public static final int STATUS_IDLE = PrinterInfoProto.STATUS_IDLE;
  
      /** Printer status: the printer is busy printing. */
      public static final int STATUS_BUSY = PrinterInfoProto.STATUS_BUSY;
  
      /** Printer status: the printer is not available. */
      public static final int STATUS_UNAVAILABLE = PrinterInfoProto.STATUS_UNAVAILABLE;
  
      private final @NonNull PrinterId mId;
  
      /** Resource inside the printer's services's package to be used as an icon */
      private final int mIconResourceId;
  
      /** If a custom icon can be loaded for the printer */
      private final boolean mHasCustomPrinterIcon;
  
      /** The generation of the icon in the cache. */
      private final int mCustomPrinterIconGen;
  
      /** Intent that launches the activity showing more information about the printer. */
      private final @Nullable PendingIntent mInfoIntent;
  
      private final @NonNull String mName;
  
      private final @Status int mStatus;
  
      private final @Nullable String mDescription;
  
      private final @Nullable PrinterCapabilitiesInfo mCapabilities;
  
      private PrinterInfo(@NonNull PrinterId printerId, @NonNull String name, @Status int status,
              int iconResourceId, boolean hasCustomPrinterIcon, String description,
              PendingIntent infoIntent, PrinterCapabilitiesInfo capabilities,
              int customPrinterIconGen) {
          mId = printerId;
          mName = name;
          mStatus = status;
          mIconResourceId = iconResourceId;
          mHasCustomPrinterIcon = hasCustomPrinterIcon;
          mDescription = description;
          mInfoIntent = infoIntent;
          mCapabilities = capabilities;
          mCustomPrinterIconGen = customPrinterIconGen;
      }
  
      /**
       * Get the globally unique printer id.
       *
       * @return The printer id.
       */
      public @NonNull PrinterId getId() {
          return mId;
      }
  
      /**
       * Get the icon to be used for this printer. If no per printer icon is available, the printer's
       * service's icon is returned. If the printer has a custom icon this icon might get requested
       * asynchronously. Once the icon is loaded the discovery sessions will be notified that the
       * printer changed.
       *
       * @param context The context that will be using the icons
       * @return The icon to be used for the printer or null if no icon could be found.
       * @hide
       */
      @TestApi
      public @Nullable Drawable loadIcon(@NonNull Context context) {
          Drawable drawable = null;
          PackageManager packageManager = context.getPackageManager();
  
          if (mHasCustomPrinterIcon) {
              PrintManager printManager = (PrintManager) context
                      .getSystemService(Context.PRINT_SERVICE);
  
              Icon icon = printManager.getCustomPrinterIcon(mId);
  
              if (icon != null) {
                  drawable = icon.loadDrawable(context);
              }
          }
  
          if (drawable == null) {
              try {
                  String packageName = mId.getServiceName().getPackageName();
                  PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
                  ApplicationInfo appInfo = packageInfo.applicationInfo;
  
                  // If no custom icon is available, try the icon from the resources
                  if (mIconResourceId != 0) {
                      drawable = packageManager.getDrawable(packageName, mIconResourceId, appInfo);
                  }
  
                  // Fall back to the printer's service's icon if no per printer icon could be found
                  if (drawable == null) {
                      drawable = appInfo.loadIcon(packageManager);
                  }
              } catch (NameNotFoundException e) {
              }
          }
  
          return drawable;
      }
  
      /**
       * Check if the printer has a custom printer icon.
       *
       * @return {@code true} iff the printer has a custom printer icon.
       *
       * @hide
       */
      public boolean getHasCustomPrinterIcon() {
          return mHasCustomPrinterIcon;
      }
  
      /**
       * Get the printer name.
       *
       * @return The printer name.
       */
      public @NonNull String getName() {
          return mName;
      }
  
      /**
       * Gets the printer status.
       *
       * @return The status.
       *
       * @see #STATUS_BUSY
       * @see #STATUS_IDLE
       * @see #STATUS_UNAVAILABLE
       */
      public @Status int getStatus() {
          return mStatus;
      }
  
      /**
       * Gets the  printer description.
       *
       * @return The description.
       */
      public @Nullable String getDescription() {
          return mDescription;
      }
  
      /**
       * Get the {@link PendingIntent} that launches the activity showing more information about the
       * printer.
       *
       * @return the {@link PendingIntent} that launches the activity showing more information about
       *         the printer or null if it is not configured
       * @hide
       */
      public @Nullable PendingIntent getInfoIntent() {
          return mInfoIntent;
      }
  
      /**
       * Gets the printer capabilities.
       *
       * @return The capabilities.
       */
      public @Nullable PrinterCapabilitiesInfo getCapabilities() {
          return mCapabilities;
      }
  
      /**
       * Check if printerId is valid.
       *
       * @param printerId The printerId that might be valid
       * @return The valid printerId
       * @throws IllegalArgumentException if printerId is not valid.
       */
      private static @NonNull PrinterId checkPrinterId(PrinterId printerId) {
          return Preconditions.checkNotNull(printerId, "printerId cannot be null.");
      }
  
      /**
       * Check if status is valid.
       *
       * @param status The status that might be valid
       * @return The valid status
       * @throws IllegalArgumentException if status is not valid.
       */
      private static @Status int checkStatus(int status) {
          if (!(status == STATUS_IDLE
                  || status == STATUS_BUSY
                  || status == STATUS_UNAVAILABLE)) {
              throw new IllegalArgumentException("status is invalid.");
          }
  
          return status;
      }
  
      /**
       * Check if name is valid.
       *
       * @param name The name that might be valid
       * @return The valid name
       * @throws IllegalArgumentException if name is not valid.
       */
      private static @NonNull String checkName(String name) {
          return Preconditions.checkStringNotEmpty(name, "name cannot be empty.");
      }
  
      private PrinterInfo(Parcel parcel) {
          // mName can be null due to unchecked set in Builder.setName and status can be invalid
          // due to unchecked set in Builder.setStatus, hence we can only check mId for a valid state
          mId = checkPrinterId((PrinterId) parcel.readParcelable(null));
          mName = checkName(parcel.readString());
          mStatus = checkStatus(parcel.readInt());
          mDescription = parcel.readString();
          mCapabilities = parcel.readParcelable(null);
          mIconResourceId = parcel.readInt();
          mHasCustomPrinterIcon = parcel.readByte() != 0;
          mCustomPrinterIconGen = parcel.readInt();
          mInfoIntent = parcel.readParcelable(null);
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(Parcel parcel, int flags) {
          parcel.writeParcelable(mId, flags);
          parcel.writeString(mName);
          parcel.writeInt(mStatus);
          parcel.writeString(mDescription);
          parcel.writeParcelable(mCapabilities, flags);
          parcel.writeInt(mIconResourceId);
          parcel.writeByte((byte) (mHasCustomPrinterIcon ? 1 : 0));
          parcel.writeInt(mCustomPrinterIconGen);
          parcel.writeParcelable(mInfoIntent, flags);
      }
  
      @Override
      public int hashCode() {
          final int prime = 31;
          int result = 1;
          result = prime * result + mId.hashCode();
          result = prime * result + mName.hashCode();
          result = prime * result + mStatus;
          result = prime * result + ((mDescription != null) ? mDescription.hashCode() : 0);
          result = prime * result + ((mCapabilities != null) ? mCapabilities.hashCode() : 0);
          result = prime * result + mIconResourceId;
          result = prime * result + (mHasCustomPrinterIcon ? 1 : 0);
          result = prime * result + mCustomPrinterIconGen;
          result = prime * result + ((mInfoIntent != null) ? mInfoIntent.hashCode() : 0);
          return result;
      }
  
      /**
       * Compare two {@link PrinterInfo printerInfos} in all aspects beside being null and the
       * {@link #mStatus}.
       *
       * @param other the other {@link PrinterInfo}
       * @return true iff the infos are equivalent
       * @hide
       */
      public boolean equalsIgnoringStatus(PrinterInfo other) {
          if (!mId.equals(other.mId)) {
              return false;
          }
          if (!mName.equals(other.mName)) {
             return false;
          }
          if (!TextUtils.equals(mDescription, other.mDescription)) {
              return false;
          }
          if (mCapabilities == null) {
              if (other.mCapabilities != null) {
                  return false;
              }
          } else if (!mCapabilities.equals(other.mCapabilities)) {
              return false;
          }
          if (mIconResourceId != other.mIconResourceId) {
              return false;
          }
          if (mHasCustomPrinterIcon != other.mHasCustomPrinterIcon) {
              return false;
          }
          if (mCustomPrinterIconGen != other.mCustomPrinterIconGen) {
              return false;
          }
          if (mInfoIntent == null) {
              if (other.mInfoIntent != null) {
                  return false;
              }
          } else if (!mInfoIntent.equals(other.mInfoIntent)) {
              return false;
          }
          return true;
      }
  
      @Override
      public boolean equals(Object obj) {
          if (this == obj) {
              return true;
          }
          if (obj == null) {
              return false;
          }
          if (getClass() != obj.getClass()) {
              return false;
          }
          PrinterInfo other = (PrinterInfo) obj;
          if (!equalsIgnoringStatus(other)) {
              return false;
          }
          if (mStatus != other.mStatus) {
              return false;
          }
          return true;
      }
  
      @Override
      public String toString() {
          StringBuilder builder = new StringBuilder();
          builder.append("PrinterInfo{");
          builder.append("id=").append(mId);
          builder.append(", name=").append(mName);
          builder.append(", status=").append(mStatus);
          builder.append(", description=").append(mDescription);
          builder.append(", capabilities=").append(mCapabilities);
          builder.append(", iconResId=").append(mIconResourceId);
          builder.append(", hasCustomPrinterIcon=").append(mHasCustomPrinterIcon);
          builder.append(", customPrinterIconGen=").append(mCustomPrinterIconGen);
          builder.append(", infoIntent=").append(mInfoIntent);
          builder.append("\"}");
          return builder.toString();
      }
  
      /**
       * Builder for creating of a {@link PrinterInfo}.
       */
      public static final class Builder {
          private @NonNull PrinterId mPrinterId;
          private @NonNull String mName;
          private @Status int mStatus;
          private int mIconResourceId;
          private boolean mHasCustomPrinterIcon;
          private String mDescription;
          private PendingIntent mInfoIntent;
          private PrinterCapabilitiesInfo mCapabilities;
          private int mCustomPrinterIconGen;
  
          /**
           * Constructor.
           *
           * @param printerId The printer id. Cannot be null.
           * @param name The printer name. Cannot be empty.
           * @param status The printer status. Must be a valid status.
           * @throws IllegalArgumentException If the printer id is null, or the
           * printer name is empty or the status is not a valid one.
           */
          public Builder(@NonNull PrinterId printerId, @NonNull String name, @Status int status) {
              mPrinterId = checkPrinterId(printerId);
              mName = checkName(name);
              mStatus = checkStatus(status);
          }
  
          /**
           * Constructor.
           *
           * @param other Other info from which to start building.
           */
          public Builder(@NonNull PrinterInfo other) {
              mPrinterId = other.mId;
              mName = other.mName;
              mStatus = other.mStatus;
              mIconResourceId = other.mIconResourceId;
              mHasCustomPrinterIcon = other.mHasCustomPrinterIcon;
              mDescription = other.mDescription;
              mInfoIntent = other.mInfoIntent;
              mCapabilities = other.mCapabilities;
              mCustomPrinterIconGen = other.mCustomPrinterIconGen;
          }
  
          /**
           * Sets the printer status.
           *
           * @param status The status.
           * @return This builder.
           * @see PrinterInfo#STATUS_IDLE
           * @see PrinterInfo#STATUS_BUSY
           * @see PrinterInfo#STATUS_UNAVAILABLE
           */
          public @NonNull Builder setStatus(@Status int status) {
              mStatus = checkStatus(status);
              return this;
          }
  
          /**
           * Set a drawable resource as icon for this printer. If no icon is set the printer's
           * service's icon is used for the printer.
           *
           * @param iconResourceId The resource ID of the icon.
           * @return This builder.
           * @see PrinterInfo.Builder#setHasCustomPrinterIcon
           */
          public @NonNull Builder setIconResourceId(@DrawableRes int iconResourceId) {
              mIconResourceId = Preconditions.checkArgumentNonnegative(iconResourceId,
                      "iconResourceId can't be negative");
              return this;
          }
  
          /**
           * Declares that the print service can load a custom per printer's icon. If both
           * {@link PrinterInfo.Builder#setIconResourceId} and a custom icon are set the resource icon
           * is shown while the custom icon loads but then the custom icon is used. If
           * {@link PrinterInfo.Builder#setIconResourceId} is not set the printer's service's icon is
           * shown while loading.
           * <p>
           * The icon is requested asynchronously and only when needed via
           * {@link android.printservice.PrinterDiscoverySession#onRequestCustomPrinterIcon}.
           * </p>
           *
           * @param hasCustomPrinterIcon If the printer has a custom icon or not.
           *
           * @return This builder.
           */
          public @NonNull Builder setHasCustomPrinterIcon(boolean hasCustomPrinterIcon) {
              mHasCustomPrinterIcon = hasCustomPrinterIcon;
              return this;
          }
  
          /**
           * Sets the <strong>localized</strong> printer name which
           * is shown to the user
           *
           * @param name The name.
           * @return This builder.
           */
          public @NonNull Builder setName(@NonNull String name) {
              mName = checkName(name);
              return this;
          }
  
          /**
           * Sets the <strong>localized</strong> printer description
           * which is shown to the user
           *
           * @param description The description.
           * @return This builder.
           */
          public @NonNull Builder setDescription(@NonNull String description) {
              mDescription = description;
              return this;
          }
  
          /**
           * Sets the {@link PendingIntent} that launches an activity showing more information about
           * the printer.
           *
           * @param infoIntent The {@link PendingIntent intent}.
           * @return This builder.
           */
          public @NonNull Builder setInfoIntent(@NonNull PendingIntent infoIntent) {
              mInfoIntent = infoIntent;
              return this;
          }
  
          /**
           * Sets the printer capabilities.
           *
           * @param capabilities The capabilities.
           * @return This builder.
           */
          public @NonNull Builder setCapabilities(@NonNull PrinterCapabilitiesInfo capabilities) {
              mCapabilities = capabilities;
              return this;
          }
  
          /**
           * Creates a new {@link PrinterInfo}.
           *
           * @return A new {@link PrinterInfo}.
           */
          public @NonNull PrinterInfo build() {
              return new PrinterInfo(mPrinterId, mName, mStatus, mIconResourceId,
                      mHasCustomPrinterIcon, mDescription, mInfoIntent, mCapabilities,
                      mCustomPrinterIconGen);
          }
  
          /**
           * Increments the generation number of the custom printer icon. As the {@link PrinterInfo}
           * does not match the previous one anymore, users of the {@link PrinterInfo} will reload the
           * icon if needed.
           *
           * @return This builder.
           * @hide
           */
          public @NonNull Builder incCustomPrinterIconGen() {
              mCustomPrinterIconGen++;
              return this;
          }
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<PrinterInfo> CREATOR =
              new Parcelable.Creator<PrinterInfo>() {
          @Override
          public PrinterInfo createFromParcel(Parcel parcel) {
              return new PrinterInfo(parcel);
          }
  
          @Override
          public PrinterInfo[] newArray(int size) {
              return new PrinterInfo[size];
          }
      };
  }
  