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      */
ProgressReporter(int id)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      */
addListener(@ullable IProgressListener listener)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      */
setProgress(int progress)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      */
setProgress(int progress, @Nullable CharSequence title)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      */
setProgress(int n, int m)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      */
setProgress(int n, int m, @Nullable CharSequence title)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      */
startSegment(int size)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      */
endSegment(int[] lastRange)171     public void endSegment(int[] lastRange) {
172         synchronized (this) {
173             mProgress = mSegmentRange[0] + mSegmentRange[1];
174             mSegmentRange = lastRange;
175         }
176     }
177 
getProgress()178     int getProgress() {
179         return mProgress;
180     }
181 
getSegmentRange()182     int[] getSegmentRange() {
183         return mSegmentRange;
184     }
185 
186     /**
187      * Report this entire task as being started.
188      */
start()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      */
finish()200     public void finish() {
201         synchronized (this) {
202             mState = STATE_FINISHED;
203             notifyFinished(mId, null);
204             mListeners.kill();
205         }
206     }
207 
notifyStarted(int id, Bundle extras)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 
notifyProgress(int id, int progress, Bundle extras)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 
notifyFinished(int id, Bundle extras)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