content: /*
   * Copyright (C) 2016 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 com.android.internal.util;
  
  import android.annotation.Nullable;
  import android.content.Intent;
  import android.os.Bundle;
  import android.os.IProgressListener;
  import android.os.RemoteCallbackList;
  import android.os.RemoteException;
  import android.util.MathUtils;
  
  import com.android.internal.annotations.GuardedBy;
  
  /**
   * Tracks and reports progress of a single task to a {@link IProgressListener}.
   * The reported progress of a task ranges from 0-100, but the task can be
   * segmented into smaller pieces using {@link #startSegment(int)} and
   * {@link #endSegment(int[])}, and segments can be nested.
   * <p>
   * Here's an example in action; when finished the overall task progress will be
   * at 60.
   *
   * <pre>
   * prog.setProgress(20);
   * {
   *     final int restore = prog.startSegment(40);
   *     for (int i = 0; i < N; i++) {
   *         prog.setProgress(i, N);
   *         ...
   *     }
   *     prog.endSegment(restore);
   * }
   * </pre>
   *
   * @hide
   */
  public class ProgressReporter {
      private static final int STATE_INIT = 0;
      private static final int STATE_STARTED = 1;
      private static final int STATE_FINISHED = 2;
  
      private final int mId;
  
      @GuardedBy("this")
      private final RemoteCallbackList<IProgressListener> mListeners = new RemoteCallbackList<>();
  
      @GuardedBy("this")
      private int mState = STATE_INIT;
      @GuardedBy("this")
      private int mProgress = 0;
      @GuardedBy("this")
      private Bundle mExtras = new Bundle();
  
      /**
       * Current segment range: first element is starting progress of this
       * segment, second element is length of segment.
       */
      @GuardedBy("this")
      private int[] mSegmentRange = new int[] { 0, 100 };
  
      /**
       * Create a new task with the given identifier whose progress will be
       * reported to the given listener.
       */
      public ProgressReporter(int id) {
          mId = id;
      }
  
      /**
       * Add given listener to watch for progress events. The current state will
       * be immediately dispatched to the given listener.
       */
      public void addListener(@Nullable IProgressListener listener) {
          if (listener == null) return;
          synchronized (this) {
              mListeners.register(listener);
              switch (mState) {
                  case STATE_INIT:
                      // Nothing has happened yet
                      break;
                  case STATE_STARTED:
                      try {
                          listener.onStarted(mId, null);
                          listener.onProgress(mId, mProgress, mExtras);
                      } catch (RemoteException ignored) {
                      }
                      break;
                  case STATE_FINISHED:
                      try {
                          listener.onFinished(mId, null);
                      } catch (RemoteException ignored) {
                      }
                      break;
              }
          }
      }
  
      /**
       * Set the progress of the currently active segment.
       *
       * @param progress Segment progress between 0-100.
       */
      public void setProgress(int progress) {
          setProgress(progress, 100, null);
      }
  
      /**
       * Set the progress of the currently active segment.
       *
       * @param progress Segment progress between 0-100.
       */
      public void setProgress(int progress, @Nullable CharSequence title) {
          setProgress(progress, 100, title);
      }
  
      /**
       * Set the fractional progress of the currently active segment.
       */
      public void setProgress(int n, int m) {
          setProgress(n, m, null);
      }
  
      /**
       * Set the fractional progress of the currently active segment.
       */
      public void setProgress(int n, int m, @Nullable CharSequence title) {
          synchronized (this) {
              if (mState != STATE_STARTED) {
                  throw new IllegalStateException("Must be started to change progress");
              }
              mProgress = mSegmentRange[0]
                      + MathUtils.constrain((n * mSegmentRange[1]) / m, 0, mSegmentRange[1]);
              if (title != null) {
                  mExtras.putCharSequence(Intent.EXTRA_TITLE, title);
              }
              notifyProgress(mId, mProgress, mExtras);
          }
      }
  
      /**
       * Start a new inner segment that will contribute the given range towards
       * the currently active segment. You must pass the returned value to
       * {@link #endSegment(int[])} when finished.
       */
      public int[] startSegment(int size) {
          synchronized (this) {
              final int[] lastRange = mSegmentRange;
              mSegmentRange = new int[] { mProgress, (size * mSegmentRange[1] / 100) };
              return lastRange;
          }
      }
  
      /**
       * End the current segment.
       */
      public void endSegment(int[] lastRange) {
          synchronized (this) {
              mProgress = mSegmentRange[0] + mSegmentRange[1];
              mSegmentRange = lastRange;
          }
      }
  
      int getProgress() {
          return mProgress;
      }
  
      int[] getSegmentRange() {
          return mSegmentRange;
      }
  
      /**
       * Report this entire task as being started.
       */
      public void start() {
          synchronized (this) {
              mState = STATE_STARTED;
              notifyStarted(mId, null);
              notifyProgress(mId, mProgress, mExtras);
          }
      }
  
      /**
       * Report this entire task as being finished.
       */
      public void finish() {
          synchronized (this) {
              mState = STATE_FINISHED;
              notifyFinished(mId, null);
              mListeners.kill();
          }
      }
  
      private void notifyStarted(int id, Bundle extras) {
          for (int i = mListeners.beginBroadcast() - 1; i >= 0; i--) {
              try {
                  mListeners.getBroadcastItem(i).onStarted(id, extras);
              } catch (RemoteException ignored) {
              }
          }
          mListeners.finishBroadcast();
      }
  
      private void notifyProgress(int id, int progress, Bundle extras) {
          for (int i = mListeners.beginBroadcast() - 1; i >= 0; i--) {
              try {
                  mListeners.getBroadcastItem(i).onProgress(id, progress, extras);
              } catch (RemoteException ignored) {
              }
          }
          mListeners.finishBroadcast();
      }
  
      private void notifyFinished(int id, Bundle extras) {
          for (int i = mListeners.beginBroadcast() - 1; i >= 0; i--) {
              try {
                  mListeners.getBroadcastItem(i).onFinished(id, extras);
              } catch (RemoteException ignored) {
              }
          }
          mListeners.finishBroadcast();
      }
  }
  