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.NonNull;
  import android.annotation.Nullable;
  import android.content.Context;
  import android.content.pm.ParceledListSlice;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.Message;
  import android.os.RemoteException;
  import android.util.ArrayMap;
  import android.util.Log;
  
  import java.lang.ref.WeakReference;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.LinkedHashMap;
  import java.util.List;
  
  /**
   * @hide
   */
  public final class PrinterDiscoverySession {
  
      private static final String LOG_TAG ="PrinterDiscoverySession";
  
      private static final int MSG_PRINTERS_ADDED = 1;
      private static final int MSG_PRINTERS_REMOVED = 2;
  
      private final LinkedHashMap<PrinterId, PrinterInfo> mPrinters =
              new LinkedHashMap<PrinterId, PrinterInfo>();
  
      private final IPrintManager mPrintManager;
  
      private final int mUserId;
  
      private final Handler mHandler;
  
      private IPrinterDiscoveryObserver mObserver;
  
      private OnPrintersChangeListener mListener;
  
      private boolean mIsPrinterDiscoveryStarted;
  
      public static interface OnPrintersChangeListener {
          public void onPrintersChanged();
      }
  
      PrinterDiscoverySession(IPrintManager printManager, Context context, int userId) {
          mPrintManager = printManager;
          mUserId = userId;
          mHandler = new SessionHandler(context.getMainLooper());
          mObserver = new PrinterDiscoveryObserver(this);
          try {
              mPrintManager.createPrinterDiscoverySession(mObserver, mUserId);
          } catch (RemoteException re) {
              Log.e(LOG_TAG, "Error creating printer discovery session", re);
          }
      }
  
      public final void startPrinterDiscovery(@Nullable List<PrinterId> priorityList) {
          if (isDestroyed()) {
              Log.w(LOG_TAG, "Ignoring start printers discovery - session destroyed");
              return;
          }
          if (!mIsPrinterDiscoveryStarted) {
              mIsPrinterDiscoveryStarted = true;
              try {
                  mPrintManager.startPrinterDiscovery(mObserver, priorityList, mUserId);
              } catch (RemoteException re) {
                  Log.e(LOG_TAG, "Error starting printer discovery", re);
              }
          }
      }
  
      public final void stopPrinterDiscovery() {
          if (isDestroyed()) {
              Log.w(LOG_TAG, "Ignoring stop printers discovery - session destroyed");
              return;
          }
          if (mIsPrinterDiscoveryStarted) {
              mIsPrinterDiscoveryStarted = false;
              try {
                  mPrintManager.stopPrinterDiscovery(mObserver, mUserId);
              } catch (RemoteException re) {
                  Log.e(LOG_TAG, "Error stopping printer discovery", re);
              }
          }
      }
  
      public final void startPrinterStateTracking(@NonNull PrinterId printerId) {
          if (isDestroyed()) {
              Log.w(LOG_TAG, "Ignoring start printer state tracking - session destroyed");
              return;
          }
          try {
              mPrintManager.startPrinterStateTracking(printerId, mUserId);
          } catch (RemoteException re) {
              Log.e(LOG_TAG, "Error starting printer state tracking", re);
          }
      }
  
      public final void stopPrinterStateTracking(@NonNull PrinterId printerId) {
          if (isDestroyed()) {
              Log.w(LOG_TAG, "Ignoring stop printer state tracking - session destroyed");
              return;
          }
          try {
              mPrintManager.stopPrinterStateTracking(printerId, mUserId);
          } catch (RemoteException re) {
              Log.e(LOG_TAG, "Error stopping printer state tracking", re);
          }
      }
  
      public final void validatePrinters(List<PrinterId> printerIds) {
          if (isDestroyed()) {
              Log.w(LOG_TAG, "Ignoring validate printers - session destroyed");
              return;
          }
          try {
              mPrintManager.validatePrinters(printerIds, mUserId);
          } catch (RemoteException re) {
              Log.e(LOG_TAG, "Error validating printers", re);
          }
      }
  
      public final void destroy() {
          if (isDestroyed()) {
              Log.w(LOG_TAG, "Ignoring destroy - session destroyed");
          }
          destroyNoCheck();
      }
  
      public final List<PrinterInfo> getPrinters() {
          if (isDestroyed()) {
              Log.w(LOG_TAG, "Ignoring get printers - session destroyed");
              return Collections.emptyList();
          }
          return new ArrayList<PrinterInfo>(mPrinters.values());
      }
  
      public final boolean isDestroyed() {
          throwIfNotCalledOnMainThread();
          return isDestroyedNoCheck();
      }
  
      public final boolean isPrinterDiscoveryStarted() {
          throwIfNotCalledOnMainThread();
          return mIsPrinterDiscoveryStarted;
      }
  
      public final void setOnPrintersChangeListener(OnPrintersChangeListener listener) {
          throwIfNotCalledOnMainThread();
          mListener = listener;
      }
  
      @Override
      protected final void finalize() throws Throwable {
          if (!isDestroyedNoCheck()) {
              Log.e(LOG_TAG, "Destroying leaked printer discovery session");
              destroyNoCheck();
          }
          super.finalize();
      }
  
      private boolean isDestroyedNoCheck() {
          return (mObserver == null);
      }
  
      private void destroyNoCheck() {
          stopPrinterDiscovery();
          try {
              mPrintManager.destroyPrinterDiscoverySession(mObserver, mUserId);
          } catch (RemoteException re) {
              Log.e(LOG_TAG, "Error destroying printer discovery session", re);
          } finally {
              mObserver = null;
              mPrinters.clear();
          }
      }
  
      private void handlePrintersAdded(List<PrinterInfo> addedPrinters) {
          if (isDestroyed()) {
              return;
          }
  
          // No old printers - do not bother keeping their position.
          if (mPrinters.isEmpty()) {
              final int printerCount = addedPrinters.size();
              for (int i = 0; i < printerCount; i++) {
                  PrinterInfo printer = addedPrinters.get(i);
                  mPrinters.put(printer.getId(), printer);
              }
              notifyOnPrintersChanged();
              return;
          }
  
          // Add the printers to a map.
          ArrayMap<PrinterId, PrinterInfo> addedPrintersMap =
                  new ArrayMap<PrinterId, PrinterInfo>();
          final int printerCount = addedPrinters.size();
          for (int i = 0; i < printerCount; i++) {
              PrinterInfo printer = addedPrinters.get(i);
              addedPrintersMap.put(printer.getId(), printer);
          }
  
          // Update printers we already have.
          for (PrinterId oldPrinterId : mPrinters.keySet()) {
              PrinterInfo updatedPrinter = addedPrintersMap.remove(oldPrinterId);
              if (updatedPrinter != null) {
                  mPrinters.put(oldPrinterId, updatedPrinter);
              }
          }
  
          // Add the new printers, i.e. what is left.
          mPrinters.putAll(addedPrintersMap);
  
          // Announce the change.
          notifyOnPrintersChanged();
      }
  
      private void handlePrintersRemoved(List<PrinterId> printerIds) {
          if (isDestroyed()) {
              return;
          }
          boolean printersChanged = false;
          final int removedPrinterIdCount = printerIds.size();
          for (int i = 0; i < removedPrinterIdCount; i++) {
              PrinterId removedPrinterId = printerIds.get(i);
              if (mPrinters.remove(removedPrinterId) != null) {
                  printersChanged = true;
              }
          }
          if (printersChanged) {
              notifyOnPrintersChanged();
          }
      }
  
      private void notifyOnPrintersChanged() {
          if (mListener != null) {
              mListener.onPrintersChanged();
          }
      }
  
      private static void throwIfNotCalledOnMainThread() {
          if (!Looper.getMainLooper().isCurrentThread()) {
              throw new IllegalAccessError("must be called from the main thread");
          }
      }
  
      private final class SessionHandler extends Handler {
  
          public SessionHandler(Looper looper) {
              super(looper, null, false);
          }
  
          @Override
          @SuppressWarnings("unchecked")
          public void handleMessage(Message message) {
              switch (message.what) {
                  case MSG_PRINTERS_ADDED: {
                      List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
                      handlePrintersAdded(printers);
                  } break;
  
                  case MSG_PRINTERS_REMOVED: {
                      List<PrinterId> printerIds = (List<PrinterId>) message.obj;
                      handlePrintersRemoved(printerIds);
                  } break;
              }
          }
      }
  
      public static final class PrinterDiscoveryObserver extends IPrinterDiscoveryObserver.Stub {
  
          private final WeakReference<PrinterDiscoverySession> mWeakSession;
  
          public PrinterDiscoveryObserver(PrinterDiscoverySession session) {
              mWeakSession = new WeakReference<PrinterDiscoverySession>(session);
          }
  
          @Override
          @SuppressWarnings("rawtypes")
          public void onPrintersAdded(ParceledListSlice printers) {
              PrinterDiscoverySession session = mWeakSession.get();
              if (session != null) {
                  session.mHandler.obtainMessage(MSG_PRINTERS_ADDED,
                          printers.getList()).sendToTarget();
              }
          }
  
          @Override
          @SuppressWarnings("rawtypes")
          public void onPrintersRemoved(ParceledListSlice printerIds) {
              PrinterDiscoverySession session = mWeakSession.get();
              if (session != null) {
                  session.mHandler.obtainMessage(MSG_PRINTERS_REMOVED,
                          printerIds.getList()).sendToTarget();
              }
          }
      }
  }
  