summaryrefslogtreecommitdiffstats
path: root/core/java/com/android/internal/widget/TextProgressBar.java
blob: 7ca07d47afe13c0dac27d74b2fb1cbc6bd672239 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/*
 * Copyright (C) 2008 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.widget;

import android.content.Context;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Chronometer;
import android.widget.Chronometer.OnChronometerTickListener;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.RemoteViews.RemoteView;

/**
 * Container that links together a {@link ProgressBar} and {@link Chronometer}
 * as children. It subscribes to {@link Chronometer#OnChronometerTickListener}
 * and updates the {@link ProgressBar} based on a preset finishing time.
 * <p>
 * This widget expects to contain two children with specific ids
 * {@link android.R.id.progress} and {@link android.R.id.text1}.
 * <p>
 * If the {@link Chronometer} {@link android.R.attr#layout_width} is
 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, then the
 * {@link android.R.attr#gravity} will be used to automatically move it with
 * respect to the {@link ProgressBar} position. For example, if
 * {@link android.view.Gravity#LEFT} then the {@link Chronometer} will be placed
 * just ahead of the leading edge of the {@link ProgressBar} position.
 */
@RemoteView
public class TextProgressBar extends RelativeLayout implements OnChronometerTickListener {
    public static final String TAG = "TextProgressBar"; 
    
    static final int CHRONOMETER_ID = android.R.id.text1;
    static final int PROGRESSBAR_ID = android.R.id.progress;
    
    Chronometer mChronometer = null;
    ProgressBar mProgressBar = null;
    
    long mDurationBase = -1;
    int mDuration = -1;

    boolean mChronometerFollow = false;
    int mChronometerGravity = Gravity.NO_GRAVITY;

    public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
    
    public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public TextProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TextProgressBar(Context context) {
        super(context);
    }

    /**
     * Catch any interesting children when they are added.
     */
    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        super.addView(child, index, params);
        
        int childId = child.getId();
        if (childId == CHRONOMETER_ID && child instanceof Chronometer) {
            mChronometer = (Chronometer) child;
            mChronometer.setOnChronometerTickListener(this);
            
            // Check if Chronometer should move with with ProgressBar 
            mChronometerFollow = (params.width == ViewGroup.LayoutParams.WRAP_CONTENT);
            mChronometerGravity = (mChronometer.getGravity() &
                    Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK);
            
        } else if (childId == PROGRESSBAR_ID && child instanceof ProgressBar) {
            mProgressBar = (ProgressBar) child;
        }
    }

    /**
     * Set the expected termination time of the running {@link Chronometer}.
     * This value is used to adjust the {@link ProgressBar} against the elapsed
     * time.
     * <p>
     * Call this <b>after</b> adjusting the {@link Chronometer} base, if
     * necessary.
     * 
     * @param durationBase Use the {@link SystemClock#elapsedRealtime} time
     *            base.
     */
    @android.view.RemotableViewMethod
    public void setDurationBase(long durationBase) {
        mDurationBase = durationBase;
        
        if (mProgressBar == null || mChronometer == null) {
            throw new RuntimeException("Expecting child ProgressBar with id " +
                    "'android.R.id.progress' and Chronometer id 'android.R.id.text1'");
        }
        
        // Update the ProgressBar maximum relative to Chronometer base
        mDuration = (int) (durationBase - mChronometer.getBase());
        if (mDuration <= 0) {
            mDuration = 1;
        }
        mProgressBar.setMax(mDuration);
    }
    
    /**
     * Callback when {@link Chronometer} changes, indicating that we should
     * update the {@link ProgressBar} and change the layout if necessary.
     */
    public void onChronometerTick(Chronometer chronometer) {
        if (mProgressBar == null) {
            throw new RuntimeException(
                "Expecting child ProgressBar with id 'android.R.id.progress'");
        }
        
        // Stop Chronometer if we're past duration
        long now = SystemClock.elapsedRealtime();
        if (now >= mDurationBase) {
            mChronometer.stop();
        }

        // Update the ProgressBar status
        int remaining = (int) (mDurationBase - now);
        mProgressBar.setProgress(mDuration - remaining);
        
        // Move the Chronometer if gravity is set correctly
        if (mChronometerFollow) {
            RelativeLayout.LayoutParams params;
            
            // Calculate estimate of ProgressBar leading edge position
            params = (RelativeLayout.LayoutParams) mProgressBar.getLayoutParams();
            int contentWidth = mProgressBar.getWidth() - (params.leftMargin + params.rightMargin);
            int leadingEdge = ((contentWidth * mProgressBar.getProgress()) /
                    mProgressBar.getMax()) + params.leftMargin;
            
            // Calculate any adjustment based on gravity
            int adjustLeft = 0;
            int textWidth = mChronometer.getWidth();
            if (mChronometerGravity == Gravity.END) {
                adjustLeft = -textWidth;
            } else if (mChronometerGravity == Gravity.CENTER_HORIZONTAL) {
                adjustLeft = -(textWidth / 2);
            }
            
            // Limit margin to keep text inside ProgressBar bounds
            leadingEdge += adjustLeft;
            int rightLimit = contentWidth - params.rightMargin - textWidth;
            if (leadingEdge < params.leftMargin) {
                leadingEdge = params.leftMargin;
            } else if (leadingEdge > rightLimit) {
                leadingEdge = rightLimit;
            }
            
            params = (RelativeLayout.LayoutParams) mChronometer.getLayoutParams();
            params.leftMargin = leadingEdge;
            
            // Request layout to move Chronometer
            mChronometer.requestLayout();
            
        }
    }
}