text: package frenchtoast;
  
  import android.annotation.SuppressLint;
  import android.app.Activity;
  import android.app.Application;
  import android.content.Context;
  import android.content.ContextWrapper;
  import android.os.Bundle;
  import android.support.annotation.LayoutRes;
  import android.support.annotation.MainThread;
  import android.support.annotation.NonNull;
  import android.support.annotation.StringRes;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.widget.Toast;
  import java.util.LinkedHashMap;
  import java.util.Map;
  import java.util.UUID;
  import java.util.concurrent.TimeUnit;
  
  import static android.widget.Toast.LENGTH_SHORT;
  import static frenchtoast.ToastInternals.MAIN_HANDLER;
  import static frenchtoast.ToastInternals.assertMainThread;
  import static frenchtoast.ToastInternals.checkNotNull;
  
  public final class FrenchToast {
  
    private static QueueHolder queueHolder;
  
    @MainThread public static void install(@NonNull Application application) {
      assertMainThread();
      checkNotNull(application, "application");
      if (queueHolder != null) {
        throw new IllegalStateException("Already installed.");
      }
      queueHolder = new QueueHolder();
      application.registerActivityLifecycleCallbacks(queueHolder);
    }
  
    @MainThread public static SmartToaster with(@NonNull Context context) {
      assertMainThread();
      checkNotNull(context, "context");
      Activity activity = unwrapActivity(context);
      return new ActivityToaster(activity);
    }
  
    private static Activity unwrapActivity(Context context) {
      Context appContext = context.getApplicationContext();
      Context unwrapped = context;
      for (; ; ) {
        if (unwrapped instanceof Activity) {
          return (Activity) unwrapped;
        }
        if (unwrapped == null || unwrapped == appContext || !(unwrapped instanceof ContextWrapper)) {
          throw new IllegalArgumentException(
              "Could not find Activity in the chain of wrapped contexts from " + context);
        }
        Context baseContext = ((ContextWrapper) unwrapped).getBaseContext();
        if (baseContext == unwrapped) {
          throw new IllegalArgumentException(
              "Could not find Activity in the chain of wrapped contexts from " + context);
        }
        unwrapped = baseContext;
      }
    }
  
    static final class Holder {
      boolean paused;
      LifecycleToastQueue queueOrNull;
      String savedUniqueId;
    }
  
    static final class ActivityToaster implements SmartToaster {
  
    /*
     * Constants based on https://github.com/android/platform_frameworks_base/blob/master/services/
     * core/java/com/android/server/notification/NotificationManagerService.java#L140
     */
  
      private static final int ANDROID_LONG_DELAY_MS = 3_500;
      private static final int ANDROID_SHORT_DELAY_MS = 2_000;
      private static final int IGNORED = LENGTH_SHORT;
  
      private final Activity activity;
      private long durationMs = ANDROID_LONG_DELAY_MS;
  
      ActivityToaster(Activity activity) {
        this.activity = activity;
      }
  
      @Override @MainThread public Toaster shortLength() {
        assertMainThread();
        durationMs = ANDROID_SHORT_DELAY_MS;
        return this;
      }
  
      @Override @MainThread public Toaster longLength() {
        assertMainThread();
        durationMs = ANDROID_LONG_DELAY_MS;
        return this;
      }
  
      @Override @MainThread public Toaster length(long duration, TimeUnit timeUnit) {
        assertMainThread();
        durationMs = timeUnit.toMillis(duration);
        return this;
      }
  
      @Override @MainThread public void clear() {
        assertMainThread();
        queueHolder.clear(activity);
      }
  
      @Override public Toasted showText(CharSequence text) {
        @SuppressLint("ShowToast") Toast toast =
            Toast.makeText(activity.getApplicationContext(), text, IGNORED);
        return showDipped(toast);
      }
  
      @Override public Toasted showText(@StringRes int stringResId) {
        @SuppressLint("ShowToast") Toast toast =
            Toast.makeText(activity.getApplicationContext(), stringResId, IGNORED);
        return showDipped(toast);
      }
  
      @Override public Toasted showLayout(@LayoutRes int layoutResId) {
        Context context = activity.getApplicationContext();
        View view = LayoutInflater.from(context).inflate(layoutResId, null);
        Toast toast = new Toast(context);
        toast.setView(view);
        return showDipped(toast);
      }
  
      @Override @MainThread public Toasted showDipped(Toast toast) {
        assertMainThread();
        Mixture mixture = Mixture.dip(toast);
        ToastQueue queue = queueHolder.getOrCreateActivityToastQueue(activity);
        queue.enqueue(mixture, durationMs);
        return new Toasted(queue, mixture);
      }
    }
  
    static final class QueueHolder extends ActivityLifecycleCallbacksAdapter {
  
      private static final String FRENCH_TOAST_ACTIVITY_UNIQUE_ID = "FRENCH_TOAST_ACTIVITY_UNIQUE_ID";
      final Map<Activity, Holder> createdActivities = new LinkedHashMap<>();
      final Map<String, LifecycleToastQueue> retainedQueues = new LinkedHashMap<>();
  
      final Runnable clearRetainedQueues = new Runnable() {
        @Override public void run() {
          clearRetainedQueues();
        }
      };
  
      @Override public void onActivityPaused(Activity activity) {
        Holder holder = createdActivities.get(activity);
        holder.paused = true;
        if (holder.queueOrNull != null) {
          holder.queueOrNull.pause();
        }
      }
  
      @Override public void onActivityResumed(Activity activity) {
        Holder holder = createdActivities.get(activity);
        holder.paused = false;
        if (holder.queueOrNull != null) {
          holder.queueOrNull.resume();
        }
        holder.savedUniqueId = null;
      }
  
      @Override public void onActivityDestroyed(Activity activity) {
        Holder holder = createdActivities.remove(activity);
        if (holder.queueOrNull == null) {
          return;
        }
        if (activity.isChangingConfigurations() && holder.savedUniqueId != null) {
          retainedQueues.put(holder.savedUniqueId, holder.queueOrNull);
          // onCreate() is always called from the same message as the previous onDestroy().
          MAIN_HANDLER.post(clearRetainedQueues);
        } else {
          holder.queueOrNull.clear();
        }
      }
  
      private void clearRetainedQueues() {
        for (LifecycleToastQueue queue : retainedQueues.values()) {
          queue.clear();
        }
        retainedQueues.clear();
      }
  
      @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        Holder holder = createdActivities.get(activity);
        String activityUniqueId = UUID.randomUUID().toString();
        outState.putString(FRENCH_TOAST_ACTIVITY_UNIQUE_ID, activityUniqueId);
        holder.savedUniqueId = activityUniqueId;
      }
  
      @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        Holder holder = new Holder();
        createdActivities.put(activity, holder);
        if (!retainedQueues.isEmpty()) {
          String uniqueId = savedInstanceState.getString(FRENCH_TOAST_ACTIVITY_UNIQUE_ID);
          if (uniqueId != null) {
            holder.queueOrNull = retainedQueues.remove(uniqueId);
          }
        }
      }
  
      void clear(Activity activity) {
        Holder holder = getHolderOrThrow(activity);
        if (holder.queueOrNull != null) {
          holder.queueOrNull.clear();
        }
      }
  
      ToastQueue getOrCreateActivityToastQueue(Activity activity) {
        Holder holder = getHolderOrThrow(activity);
        if (holder.queueOrNull == null) {
          LifecycleToastQueue toastQueue = new LifecycleToastQueue();
          if (holder.paused) {
            toastQueue.pause();
          }
          holder.queueOrNull = toastQueue;
        }
  
        return holder.queueOrNull;
      }
  
      private Holder getHolderOrThrow(Activity activity) {
        Holder holder = createdActivities.get(activity);
        if (holder == null) {
          throw new NullPointerException("Unknown activity "
              + activity
              + ", make sure it's not destroyed "
              + " and that you did not forget to call ActivityToasts.install() "
              + "from Application.onCreate()");
        }
        return holder;
      }
    }
  
    private FrenchToast() {
      throw new AssertionError();
    }
  }
  