/* * 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. *

* Here's an example in action; when finished the overall task progress will be * at 60. * *

 * prog.setProgress(20);
 * {
 *     final int restore = prog.startSegment(40);
 *     for (int i = 0; i < N; i++) {
 *         prog.setProgress(i, N);
 *         ...
 *     }
 *     prog.endSegment(restore);
 * }
 * 
* * @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 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(); } }