/*
* 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();
}
}