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