1 /* 2 * Copyright (C) 2008 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.widget; 18 19 import android.content.Context; 20 import android.os.SystemClock; 21 import android.util.AttributeSet; 22 import android.view.Gravity; 23 import android.view.View; 24 import android.view.ViewGroup; 25 import android.widget.Chronometer; 26 import android.widget.Chronometer.OnChronometerTickListener; 27 import android.widget.ProgressBar; 28 import android.widget.RelativeLayout; 29 import android.widget.RemoteViews.RemoteView; 30 31 /** 32 * Container that links together a {@link ProgressBar} and {@link Chronometer} 33 * as children. It subscribes to {@link Chronometer#OnChronometerTickListener} 34 * and updates the {@link ProgressBar} based on a preset finishing time. 35 * <p> 36 * This widget expects to contain two children with specific ids 37 * {@link android.R.id.progress} and {@link android.R.id.text1}. 38 * <p> 39 * If the {@link Chronometer} {@link android.R.attr#layout_width} is 40 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, then the 41 * {@link android.R.attr#gravity} will be used to automatically move it with 42 * respect to the {@link ProgressBar} position. For example, if 43 * {@link android.view.Gravity#LEFT} then the {@link Chronometer} will be placed 44 * just ahead of the leading edge of the {@link ProgressBar} position. 45 */ 46 @RemoteView 47 public class TextProgressBar extends RelativeLayout implements OnChronometerTickListener { 48 public static final String TAG = "TextProgressBar"; 49 50 static final int CHRONOMETER_ID = android.R.id.text1; 51 static final int PROGRESSBAR_ID = android.R.id.progress; 52 53 Chronometer mChronometer = null; 54 ProgressBar mProgressBar = null; 55 56 long mDurationBase = -1; 57 int mDuration = -1; 58 59 boolean mChronometerFollow = false; 60 int mChronometerGravity = Gravity.NO_GRAVITY; 61 62 public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 63 super(context, attrs, defStyleAttr, defStyleRes); 64 } 65 66 public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { 67 super(context, attrs, defStyleAttr); 68 } 69 70 public TextProgressBar(Context context, AttributeSet attrs) { 71 super(context, attrs); 72 } 73 74 public TextProgressBar(Context context) { 75 super(context); 76 } 77 78 /** 79 * Catch any interesting children when they are added. 80 */ 81 @Override 82 public void addView(View child, int index, ViewGroup.LayoutParams params) { 83 super.addView(child, index, params); 84 85 int childId = child.getId(); 86 if (childId == CHRONOMETER_ID && child instanceof Chronometer) { 87 mChronometer = (Chronometer) child; 88 mChronometer.setOnChronometerTickListener(this); 89 90 // Check if Chronometer should move with with ProgressBar 91 mChronometerFollow = (params.width == ViewGroup.LayoutParams.WRAP_CONTENT); 92 mChronometerGravity = (mChronometer.getGravity() & 93 Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK); 94 95 } else if (childId == PROGRESSBAR_ID && child instanceof ProgressBar) { 96 mProgressBar = (ProgressBar) child; 97 } 98 } 99 100 /** 101 * Set the expected termination time of the running {@link Chronometer}. 102 * This value is used to adjust the {@link ProgressBar} against the elapsed 103 * time. 104 * <p> 105 * Call this <b>after</b> adjusting the {@link Chronometer} base, if 106 * necessary. 107 * 108 * @param durationBase Use the {@link SystemClock#elapsedRealtime} time 109 * base. 110 */ 111 @android.view.RemotableViewMethod 112 public void setDurationBase(long durationBase) { 113 mDurationBase = durationBase; 114 115 if (mProgressBar == null || mChronometer == null) { 116 throw new RuntimeException("Expecting child ProgressBar with id " + 117 "'android.R.id.progress' and Chronometer id 'android.R.id.text1'"); 118 } 119 120 // Update the ProgressBar maximum relative to Chronometer base 121 mDuration = (int) (durationBase - mChronometer.getBase()); 122 if (mDuration <= 0) { 123 mDuration = 1; 124 } 125 mProgressBar.setMax(mDuration); 126 } 127 128 /** 129 * Callback when {@link Chronometer} changes, indicating that we should 130 * update the {@link ProgressBar} and change the layout if necessary. 131 */ 132 public void onChronometerTick(Chronometer chronometer) { 133 if (mProgressBar == null) { 134 throw new RuntimeException( 135 "Expecting child ProgressBar with id 'android.R.id.progress'"); 136 } 137 138 // Stop Chronometer if we're past duration 139 long now = SystemClock.elapsedRealtime(); 140 if (now >= mDurationBase) { 141 mChronometer.stop(); 142 } 143 144 // Update the ProgressBar status 145 int remaining = (int) (mDurationBase - now); 146 mProgressBar.setProgress(mDuration - remaining); 147 148 // Move the Chronometer if gravity is set correctly 149 if (mChronometerFollow) { 150 RelativeLayout.LayoutParams params; 151 152 // Calculate estimate of ProgressBar leading edge position 153 params = (RelativeLayout.LayoutParams) mProgressBar.getLayoutParams(); 154 int contentWidth = mProgressBar.getWidth() - (params.leftMargin + params.rightMargin); 155 int leadingEdge = ((contentWidth * mProgressBar.getProgress()) / 156 mProgressBar.getMax()) + params.leftMargin; 157 158 // Calculate any adjustment based on gravity 159 int adjustLeft = 0; 160 int textWidth = mChronometer.getWidth(); 161 if (mChronometerGravity == Gravity.END) { 162 adjustLeft = -textWidth; 163 } else if (mChronometerGravity == Gravity.CENTER_HORIZONTAL) { 164 adjustLeft = -(textWidth / 2); 165 } 166 167 // Limit margin to keep text inside ProgressBar bounds 168 leadingEdge += adjustLeft; 169 int rightLimit = contentWidth - params.rightMargin - textWidth; 170 if (leadingEdge < params.leftMargin) { 171 leadingEdge = params.leftMargin; 172 } else if (leadingEdge > rightLimit) { 173 leadingEdge = rightLimit; 174 } 175 176 params = (RelativeLayout.LayoutParams) mChronometer.getLayoutParams(); 177 params.leftMargin = leadingEdge; 178 179 // Request layout to move Chronometer 180 mChronometer.requestLayout(); 181 182 } 183 } 184 } 185