content: package com.androidadvance.topsnackbar;
  
  /*
   * Copyright (C) 2015 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.
   */
  
  import android.os.Handler;
  import android.os.Looper;
  import android.os.Message;
  
  import java.lang.ref.WeakReference;
  
  /**
   * Manages {@link TSnackbar}s.
   */
  class SnackbarManager {
  
      private static final int MSG_TIMEOUT = 0;
  
      private static final int SHORT_DURATION_MS = 1500;
      private static final int LONG_DURATION_MS = 2750;
  
      private static SnackbarManager sSnackbarManager;
  
      static SnackbarManager getInstance() {
          if (sSnackbarManager == null) {
              sSnackbarManager = new SnackbarManager();
          }
          return sSnackbarManager;
      }
  
      private final Object mLock;
      private final Handler mHandler;
  
      private SnackbarRecord mCurrentSnackbar;
      private SnackbarRecord mNextSnackbar;
  
      private SnackbarManager() {
          mLock = new Object();
          mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
              @Override
              public boolean handleMessage(Message message) {
                  switch (message.what) {
                      case MSG_TIMEOUT:
                          handleTimeout((SnackbarRecord) message.obj);
                          return true;
                  }
                  return false;
              }
          });
      }
  
      interface Callback {
          void show();
  
          void dismiss(int event);
      }
  
      public void show(int duration, Callback callback) {
          synchronized (mLock) {
              if (isCurrentSnackbar(callback)) {
                  // Means that the callback is already in the queue. We'll just update the duration
                  mCurrentSnackbar.duration = duration;
                  // If this is the TSnackbar currently being shown, call re-schedule it's
                  // timeout
                  mHandler.removeCallbacksAndMessages(mCurrentSnackbar);
                  scheduleTimeoutLocked(mCurrentSnackbar);
                  return;
              } else if (isNextSnackbar(callback)) {
                  // We'll just update the duration
                  mNextSnackbar.duration = duration;
              } else {
                  // Else, we need to create a new record and queue it
                  mNextSnackbar = new SnackbarRecord(duration, callback);
              }
  
              if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar,
                      TSnackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) {
                  // If we currently have a TSnackbar, try and cancel it and wait in line
                  return;
              } else {
                  // Clear out the current snackbar
                  mCurrentSnackbar = null;
                  // Otherwise, just show it now
                  showNextSnackbarLocked();
              }
          }
      }
  
      public void dismiss(Callback callback, int event) {
          synchronized (mLock) {
              if (isCurrentSnackbar(callback)) {
                  cancelSnackbarLocked(mCurrentSnackbar, event);
              } else if (isNextSnackbar(callback)) {
                  cancelSnackbarLocked(mNextSnackbar, event);
              }
          }
      }
  
      /**
       * Should be called when a TSnackbar is no longer displayed. This is after any exit
       * animation has finished.
       */
      public void onDismissed(Callback callback) {
          synchronized (mLock) {
              if (isCurrentSnackbar(callback)) {
                  // If the callback is from a TSnackbar currently show, remove it and show a new one
                  mCurrentSnackbar = null;
                  if (mNextSnackbar != null) {
                      showNextSnackbarLocked();
                  }
              }
          }
      }
  
      /**
       * Should be called when a TSnackbar is being shown. This is after any entrance animation has
       * finished.
       */
      public void onShown(Callback callback) {
          synchronized (mLock) {
              if (isCurrentSnackbar(callback)) {
                  scheduleTimeoutLocked(mCurrentSnackbar);
              }
          }
      }
  
      public void cancelTimeout(Callback callback) {
          synchronized (mLock) {
              if (isCurrentSnackbar(callback)) {
                  mHandler.removeCallbacksAndMessages(mCurrentSnackbar);
              }
          }
      }
  
      public void restoreTimeout(Callback callback) {
          synchronized (mLock) {
              if (isCurrentSnackbar(callback)) {
                  scheduleTimeoutLocked(mCurrentSnackbar);
              }
          }
      }
  
      public boolean isCurrent(Callback callback) {
          synchronized (mLock) {
              return isCurrentSnackbar(callback);
          }
      }
  
      public boolean isCurrentOrNext(Callback callback) {
          synchronized (mLock) {
              return isCurrentSnackbar(callback) || isNextSnackbar(callback);
          }
      }
  
      private static class SnackbarRecord {
          private final WeakReference<Callback> callback;
          private int duration;
  
          SnackbarRecord(int duration, Callback callback) {
              this.callback = new WeakReference<>(callback);
              this.duration = duration;
          }
  
          boolean isSnackbar(Callback callback) {
              return callback != null && this.callback.get() == callback;
          }
      }
  
      private void showNextSnackbarLocked() {
          if (mNextSnackbar != null) {
              mCurrentSnackbar = mNextSnackbar;
              mNextSnackbar = null;
  
              final Callback callback = mCurrentSnackbar.callback.get();
              if (callback != null) {
                  callback.show();
              } else {
                  // The callback doesn't exist any more, clear out the TSnackbar
                  mCurrentSnackbar = null;
              }
          }
      }
  
      private boolean cancelSnackbarLocked(SnackbarRecord record, int event) {
          final Callback callback = record.callback.get();
          if (callback != null) {
              callback.dismiss(event);
              return true;
          }
          return false;
      }
  
      private boolean isCurrentSnackbar(Callback callback) {
          return mCurrentSnackbar != null && mCurrentSnackbar.isSnackbar(callback);
      }
  
      private boolean isNextSnackbar(Callback callback) {
          return mNextSnackbar != null && mNextSnackbar.isSnackbar(callback);
      }
  
      private void scheduleTimeoutLocked(SnackbarRecord r) {
          if (r.duration == TSnackbar.LENGTH_INDEFINITE) {
              // If we're set to indefinite, we don't want to set a timeout
              return;
          }
  
          int durationMs = LONG_DURATION_MS;
          if (r.duration > 0) {
              durationMs = r.duration;
          } else if (r.duration == TSnackbar.LENGTH_SHORT) {
              durationMs = SHORT_DURATION_MS;
          }
          mHandler.removeCallbacksAndMessages(r);
          mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r), durationMs);
      }
  
      private void handleTimeout(SnackbarRecord record) {
          synchronized (mLock) {
              if (mCurrentSnackbar == record || mNextSnackbar == record) {
                  cancelSnackbarLocked(record, TSnackbar.Callback.DISMISS_EVENT_TIMEOUT);
              }
          }
      }
  
  }
  