Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.util;
     18 
     19 import android.annotation.Nullable;
     20 import android.content.Intent;
     21 import android.os.Bundle;
     22 import android.os.IProgressListener;
     23 import android.os.RemoteCallbackList;
     24 import android.os.RemoteException;
     25 import android.util.MathUtils;
     26 
     27 import com.android.internal.annotations.GuardedBy;
     28 
     29 /**
     30  * Tracks and reports progress of a single task to a {@link IProgressListener}.
     31  * The reported progress of a task ranges from 0-100, but the task can be
     32  * segmented into smaller pieces using {@link #startSegment(int)} and
     33  * {@link #endSegment(int[])}, and segments can be nested.
     34  * <p>
     35  * Here's an example in action; when finished the overall task progress will be
     36  * at 60.
     37  *
     38  * <pre>
     39  * prog.setProgress(20);
     40  * {
     41  *     final int restore = prog.startSegment(40);
     42  *     for (int i = 0; i < N; i++) {
     43  *         prog.setProgress(i, N);
     44  *         ...
     45  *     }
     46  *     prog.endSegment(restore);
     47  * }
     48  * </pre>
     49  *
     50  * @hide
     51  */
     52 public class ProgressReporter {
     53     private static final int STATE_INIT = 0;
     54     private static final int STATE_STARTED = 1;
     55     private static final int STATE_FINISHED = 2;
     56 
     57     private final int mId;
     58 
     59     @GuardedBy("this")
     60     private final RemoteCallbackList<IProgressListener> mListeners = new RemoteCallbackList<>();
     61 
     62     @GuardedBy("this")
     63     private int mState = STATE_INIT;
     64     @GuardedBy("this")
     65     private int mProgress = 0;
     66     @GuardedBy("this")
     67     private Bundle mExtras = new Bundle();
     68 
     69     /**
     70      * Current segment range: first element is starting progress of this
     71      * segment, second element is length of segment.
     72      */
     73     @GuardedBy("this")
     74     private int[] mSegmentRange = new int[] { 0, 100 };
     75 
     76     /**
     77      * Create a new task with the given identifier whose progress will be
     78      * reported to the given listener.
     79      */
     80     public ProgressReporter(int id) {
     81         mId = id;
     82     }
     83 
     84     /**
     85      * Add given listener to watch for progress events. The current state will
     86      * be immediately dispatched to the given listener.
     87      */
     88     public void addListener(@Nullable IProgressListener listener) {
     89         if (listener == null) return;
     90         synchronized (this) {
     91             mListeners.register(listener);
     92             switch (mState) {
     93                 case STATE_INIT:
     94                     // Nothing has happened yet
     95                     break;
     96                 case STATE_STARTED:
     97                     try {
     98                         listener.onStarted(mId, null);
     99                         listener.onProgress(mId, mProgress, mExtras);
    100                     } catch (RemoteException ignored) {
    101                     }
    102                     break;
    103                 case STATE_FINISHED:
    104                     try {
    105                         listener.onFinished(mId, null);
    106                     } catch (RemoteException ignored) {
    107                     }
    108                     break;
    109             }
    110         }
    111     }
    112 
    113     /**
    114      * Set the progress of the currently active segment.
    115      *
    116      * @param progress Segment progress between 0-100.
    117      */
    118     public void setProgress(int progress) {
    119         setProgress(progress, 100, null);
    120     }
    121 
    122     /**
    123      * Set the progress of the currently active segment.
    124      *
    125      * @param progress Segment progress between 0-100.
    126      */
    127     public void setProgress(int progress, @Nullable CharSequence title) {
    128         setProgress(progress, 100, title);
    129     }
    130 
    131     /**
    132      * Set the fractional progress of the currently active segment.
    133      */
    134     public void setProgress(int n, int m) {
    135         setProgress(n, m, null);
    136     }
    137 
    138     /**
    139      * Set the fractional progress of the currently active segment.
    140      */
    141     public void setProgress(int n, int m, @Nullable CharSequence title) {
    142         synchronized (this) {
    143             if (mState != STATE_STARTED) {
    144                 throw new IllegalStateException("Must be started to change progress");
    145             }
    146             mProgress = mSegmentRange[0]
    147                     + MathUtils.constrain((n * mSegmentRange[1]) / m, 0, mSegmentRange[1]);
    148             if (title != null) {
    149                 mExtras.putCharSequence(Intent.EXTRA_TITLE, title);
    150             }
    151             notifyProgress(mId, mProgress, mExtras);
    152         }
    153     }
    154 
    155     /**
    156      * Start a new inner segment that will contribute the given range towards
    157      * the currently active segment. You must pass the returned value to
    158      * {@link #endSegment(int[])} when finished.
    159      */
    160     public int[] startSegment(int size) {
    161         synchronized (this) {
    162             final int[] lastRange = mSegmentRange;
    163             mSegmentRange = new int[] { mProgress, (size * mSegmentRange[1] / 100) };
    164             return lastRange;
    165         }
    166     }
    167 
    168     /**
    169      * End the current segment.
    170      */
    171     public void endSegment(int[] lastRange) {
    172         synchronized (this) {
    173             mProgress = mSegmentRange[0] + mSegmentRange[1];
    174             mSegmentRange = lastRange;
    175         }
    176     }
    177 
    178     int getProgress() {
    179         return mProgress;
    180     }
    181 
    182     int[] getSegmentRange() {
    183         return mSegmentRange;
    184     }
    185 
    186     /**
    187      * Report this entire task as being started.
    188      */
    189     public void start() {
    190         synchronized (this) {
    191             mState = STATE_STARTED;
    192             notifyStarted(mId, null);
    193             notifyProgress(mId, mProgress, mExtras);
    194         }
    195     }
    196 
    197     /**
    198      * Report this entire task as being finished.
    199      */
    200     public void finish() {
    201         synchronized (this) {
    202             mState = STATE_FINISHED;
    203             notifyFinished(mId, null);
    204             mListeners.kill();
    205         }
    206     }
    207 
    208     private void notifyStarted(int id, Bundle extras) {
    209         for (int i = mListeners.beginBroadcast() - 1; i >= 0; i--) {
    210             try {
    211                 mListeners.getBroadcastItem(i).onStarted(id, extras);
    212             } catch (RemoteException ignored) {
    213             }
    214         }
    215         mListeners.finishBroadcast();
    216     }
    217 
    218     private void notifyProgress(int id, int progress, Bundle extras) {
    219         for (int i = mListeners.beginBroadcast() - 1; i >= 0; i--) {
    220             try {
    221                 mListeners.getBroadcastItem(i).onProgress(id, progress, extras);
    222             } catch (RemoteException ignored) {
    223             }
    224         }
    225         mListeners.finishBroadcast();
    226     }
    227 
    228     private void notifyFinished(int id, Bundle extras) {
    229         for (int i = mListeners.beginBroadcast() - 1; i >= 0; i--) {
    230             try {
    231                 mListeners.getBroadcastItem(i).onFinished(id, extras);
    232             } catch (RemoteException ignored) {
    233             }
    234         }
    235         mListeners.finishBroadcast();
    236     }
    237 }
    238