1/*
2 * Copyright (C) 2011 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
17package com.android.gallery3d.app;
18
19import android.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.BitmapFactory;
22import android.graphics.Canvas;
23import android.graphics.Paint;
24import android.graphics.Rect;
25import android.util.DisplayMetrics;
26import android.view.MotionEvent;
27import android.view.View;
28
29import com.android.gallery3d.R;
30import com.android.gallery3d.common.Utils;
31
32/**
33 * The time bar view, which includes the current and total time, the progress
34 * bar, and the scrubber.
35 */
36public class TimeBar extends View {
37
38    public interface Listener {
39        void onScrubbingStart();
40
41        void onScrubbingMove(int time);
42
43        void onScrubbingEnd(int time, int start, int end);
44    }
45
46    // Padding around the scrubber to increase its touch target
47    private static final int SCRUBBER_PADDING_IN_DP = 10;
48
49    // The total padding, top plus bottom
50    private static final int V_PADDING_IN_DP = 30;
51
52    private static final int TEXT_SIZE_IN_DP = 14;
53
54    protected final Listener mListener;
55
56    // the bars we use for displaying the progress
57    protected final Rect mProgressBar;
58    protected final Rect mPlayedBar;
59
60    protected final Paint mProgressPaint;
61    protected final Paint mPlayedPaint;
62    protected final Paint mTimeTextPaint;
63
64    protected final Bitmap mScrubber;
65    protected int mScrubberPadding; // adds some touch tolerance around the
66                                    // scrubber
67
68    protected int mScrubberLeft;
69    protected int mScrubberTop;
70    protected int mScrubberCorrection;
71    protected boolean mScrubbing;
72    protected boolean mShowTimes;
73    protected boolean mShowScrubber;
74
75    protected int mTotalTime;
76    protected int mCurrentTime;
77
78    protected final Rect mTimeBounds;
79
80    protected int mVPaddingInPx;
81
82    public TimeBar(Context context, Listener listener) {
83        super(context);
84        mListener = Utils.checkNotNull(listener);
85
86        mShowTimes = true;
87        mShowScrubber = true;
88
89        mProgressBar = new Rect();
90        mPlayedBar = new Rect();
91
92        mProgressPaint = new Paint();
93        mProgressPaint.setColor(0xFF808080);
94        mPlayedPaint = new Paint();
95        mPlayedPaint.setColor(0xFFFFFFFF);
96
97        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
98        float textSizeInPx = metrics.density * TEXT_SIZE_IN_DP;
99        mTimeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
100        mTimeTextPaint.setColor(0xFFCECECE);
101        mTimeTextPaint.setTextSize(textSizeInPx);
102        mTimeTextPaint.setTextAlign(Paint.Align.CENTER);
103
104        mTimeBounds = new Rect();
105        mTimeTextPaint.getTextBounds("0:00:00", 0, 7, mTimeBounds);
106
107        mScrubber = BitmapFactory.decodeResource(getResources(), R.drawable.scrubber_knob);
108        mScrubberPadding = (int) (metrics.density * SCRUBBER_PADDING_IN_DP);
109
110        mVPaddingInPx = (int) (metrics.density * V_PADDING_IN_DP);
111    }
112
113    private void update() {
114        mPlayedBar.set(mProgressBar);
115
116        if (mTotalTime > 0) {
117            mPlayedBar.right =
118                    mPlayedBar.left + (int) ((mProgressBar.width() * (long) mCurrentTime) / mTotalTime);
119        } else {
120            mPlayedBar.right = mProgressBar.left;
121        }
122
123        if (!mScrubbing) {
124            mScrubberLeft = mPlayedBar.right - mScrubber.getWidth() / 2;
125        }
126        invalidate();
127    }
128
129    /**
130     * @return the preferred height of this view, including invisible padding
131     */
132    public int getPreferredHeight() {
133        return mTimeBounds.height() + mVPaddingInPx + mScrubberPadding;
134    }
135
136    /**
137     * @return the height of the time bar, excluding invisible padding
138     */
139    public int getBarHeight() {
140        return mTimeBounds.height() + mVPaddingInPx;
141    }
142
143    public void setTime(int currentTime, int totalTime,
144            int trimStartTime, int trimEndTime) {
145        if (mCurrentTime == currentTime && mTotalTime == totalTime) {
146            return;
147        }
148        mCurrentTime = currentTime;
149        mTotalTime = totalTime;
150        update();
151    }
152
153    private boolean inScrubber(float x, float y) {
154        int scrubberRight = mScrubberLeft + mScrubber.getWidth();
155        int scrubberBottom = mScrubberTop + mScrubber.getHeight();
156        return mScrubberLeft - mScrubberPadding < x && x < scrubberRight + mScrubberPadding
157                && mScrubberTop - mScrubberPadding < y && y < scrubberBottom + mScrubberPadding;
158    }
159
160    private void clampScrubber() {
161        int half = mScrubber.getWidth() / 2;
162        int max = mProgressBar.right - half;
163        int min = mProgressBar.left - half;
164        mScrubberLeft = Math.min(max, Math.max(min, mScrubberLeft));
165    }
166
167    private int getScrubberTime() {
168        return (int) ((long) (mScrubberLeft + mScrubber.getWidth() / 2 - mProgressBar.left)
169                * mTotalTime / mProgressBar.width());
170    }
171
172    @Override
173    protected void onLayout(boolean changed, int l, int t, int r, int b) {
174        int w = r - l;
175        int h = b - t;
176        if (!mShowTimes && !mShowScrubber) {
177            mProgressBar.set(0, 0, w, h);
178        } else {
179            int margin = mScrubber.getWidth() / 3;
180            if (mShowTimes) {
181                margin += mTimeBounds.width();
182            }
183            int progressY = (h + mScrubberPadding) / 2;
184            mScrubberTop = progressY - mScrubber.getHeight() / 2 + 1;
185            mProgressBar.set(
186                    getPaddingLeft() + margin, progressY,
187                    w - getPaddingRight() - margin, progressY + 4);
188        }
189        update();
190    }
191
192    @Override
193    protected void onDraw(Canvas canvas) {
194        // draw progress bars
195        canvas.drawRect(mProgressBar, mProgressPaint);
196        canvas.drawRect(mPlayedBar, mPlayedPaint);
197
198        // draw scrubber and timers
199        if (mShowScrubber) {
200            canvas.drawBitmap(mScrubber, mScrubberLeft, mScrubberTop, null);
201        }
202        if (mShowTimes) {
203            canvas.drawText(
204                    stringForTime(mCurrentTime),
205                            mTimeBounds.width() / 2 + getPaddingLeft(),
206                            mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1,
207                    mTimeTextPaint);
208            canvas.drawText(
209                    stringForTime(mTotalTime),
210                            getWidth() - getPaddingRight() - mTimeBounds.width() / 2,
211                            mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1,
212                    mTimeTextPaint);
213        }
214    }
215
216    @Override
217    public boolean onTouchEvent(MotionEvent event) {
218        if (mShowScrubber) {
219            int x = (int) event.getX();
220            int y = (int) event.getY();
221
222            switch (event.getAction()) {
223                case MotionEvent.ACTION_DOWN: {
224                    mScrubberCorrection = inScrubber(x, y)
225                            ? x - mScrubberLeft
226                            : mScrubber.getWidth() / 2;
227                    mScrubbing = true;
228                    mListener.onScrubbingStart();
229                }
230                // fall-through
231                case MotionEvent.ACTION_MOVE: {
232                    mScrubberLeft = x - mScrubberCorrection;
233                    clampScrubber();
234                    mCurrentTime = getScrubberTime();
235                    mListener.onScrubbingMove(mCurrentTime);
236                    invalidate();
237                    return true;
238                }
239                case MotionEvent.ACTION_CANCEL:
240                case MotionEvent.ACTION_UP: {
241                    mListener.onScrubbingEnd(getScrubberTime(), 0, 0);
242                    mScrubbing = false;
243                    return true;
244                }
245            }
246        }
247        return false;
248    }
249
250    protected String stringForTime(long millis) {
251        int totalSeconds = (int) millis / 1000;
252        int seconds = totalSeconds % 60;
253        int minutes = (totalSeconds / 60) % 60;
254        int hours = totalSeconds / 3600;
255        if (hours > 0) {
256            return String.format("%d:%02d:%02d", hours, minutes, seconds).toString();
257        } else {
258            return String.format("%02d:%02d", minutes, seconds).toString();
259        }
260    }
261
262    public void setSeekable(boolean canSeek) {
263        mShowScrubber = canSeek;
264    }
265
266}
267