OSDN Git Service

Eleven: rebrand step 2: update file contents
[android-x86/packages-apps-Eleven.git] / src / org / lineageos / eleven / widgets / PlayPauseProgressButton.java
1 /*
2 * Copyright (c) 2013, The Linux Foundation. All rights reserved.
3 * Copyright (C) 2015 The CyanogenMod Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.lineageos.eleven.widgets;
18
19 import android.content.Context;
20 import android.util.AttributeSet;
21 import android.util.Log;
22 import android.view.MotionEvent;
23 import android.view.ViewConfiguration;
24 import android.widget.FrameLayout;
25 import android.widget.ProgressBar;
26
27 import org.lineageos.eleven.R;
28 import org.lineageos.eleven.utils.MusicUtils;
29
30 /**
31  * This class handles the playpause button as well as the circular progress bar
32  * it self-updates the progress bar but the containing activity/fragment
33  * needs to add code to pause/resume this button to prevent unnecessary
34  * updates while the activity/fragment is not visible
35  */
36 public class PlayPauseProgressButton extends FrameLayout {
37     private static String TAG = PlayPauseProgressButton.class.getSimpleName();
38     private static boolean DEBUG = false;
39     private static final int REVOLUTION_IN_DEGREES = 360;
40     private static final int HALF_REVOLUTION_IN_DEGREES = REVOLUTION_IN_DEGREES / 2;
41
42     private ProgressBar mProgressBar;
43     private PlayPauseButton mPlayPauseButton;
44     private Runnable mUpdateProgress;
45     private boolean mPaused;
46
47     private final int mSmallDistance;
48     private float mDragPercentage = 0.0f;
49     private boolean mDragEnabled = false;
50     private boolean mDragging = false;
51     private float mDownAngle;
52     private float mDragAngle;
53     private float mDownX;
54     private float mDownY;
55     private int mWidth;
56     private long mCurrentSongDuration;
57     private long mCurrentSongProgress;
58
59     public PlayPauseProgressButton(Context context, AttributeSet attrs) {
60         super(context, attrs);
61
62         // set enabled to false as default so that calling enableAndShow will execute
63         setEnabled(false);
64
65         // set paused to false since we shouldn't be typically created while not visible
66         mPaused = false;
67
68         mSmallDistance = ViewConfiguration.get(context).getScaledTouchSlop();
69     }
70
71     @Override
72     protected void onFinishInflate() {
73         super.onFinishInflate();
74
75         mPlayPauseButton = (PlayPauseButton)findViewById(R.id.action_button_play);
76         mProgressBar = (ProgressBar)findViewById(R.id.circularProgressBar);
77     }
78
79     @Override
80     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
81         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
82
83         // Make the play pause button size dependent on the container size
84         int horizontalPadding = getMeasuredWidth() / 4;
85         int verticalPadding = getMeasuredHeight() / 4;
86         mPlayPauseButton.setPadding(
87                 horizontalPadding, horizontalPadding,
88                 verticalPadding, verticalPadding);
89
90         // rotate the progress bar 90 degrees counter clockwise so that the
91         // starting position is at the top
92         mProgressBar.setPivotX(mProgressBar.getMeasuredWidth() / 2);
93         mProgressBar.setPivotY(mProgressBar.getMeasuredHeight() / 2);
94         mProgressBar.setRotation(-90);
95     }
96
97     /**
98      * Enable and shows the container
99      */
100     public void enableAndShow() {
101         // enable
102         setEnabled(true);
103
104         // make our view visible
105         setVisibility(VISIBLE);
106     }
107
108     /**
109      * Disables and sets the visibility to gone for the container
110      */
111     public void disableAndHide() {
112         // disable
113         setEnabled(false);
114
115         // hide our view
116         setVisibility(GONE);
117     }
118
119     /**
120      * Sets whether the user can drag the progress in a circular motion to seek the track
121      */
122     public void setDragEnabled(boolean enabled) {
123         mDragEnabled = enabled;
124     }
125
126     /**
127      * @return true if the user is actively dragging to seek
128      */
129     public boolean isDragging() {
130         return mDragEnabled && mDragging;
131     }
132
133     /**
134      * @return how far the user has dragged in the track in ms
135      */
136     public long getDragProgressInMs() {
137         return (long)(mDragPercentage * mCurrentSongDuration);
138     }
139
140     @Override
141     public void setEnabled(boolean enabled) {
142         // if the enabled state isn't changed, quit
143         if (enabled == isEnabled()) return;
144
145         super.setEnabled(enabled);
146
147         // signal our state has changed
148         onStateChanged();
149     }
150
151     /**
152      * Pauses the progress bar periodic update logic
153      */
154     public void pause() {
155         if (!mPaused) {
156             mPaused = true;
157
158             // signal our state has changed
159             onStateChanged();
160         }
161     }
162
163     /**
164      * Resumes the progress bar periodic update logic
165      */
166     public void resume() {
167         if (mPaused) {
168             mPaused = false;
169
170             // signal our state has changed
171             onStateChanged();
172         }
173     }
174
175     /**
176      * @return play pause button
177      */
178     public PlayPauseButton getPlayPauseButton() {
179         return mPlayPauseButton;
180     }
181
182     /**
183      * Signaled if the state has changed (either the enabled or paused flag)
184      * When the state changes, we either kick off the updates or remove them based on those flags
185      */
186     private void onStateChanged() {
187         // if we are enabled and not paused
188         if (isEnabled() && !mPaused) {
189             // update the state of the progress bar and play/pause button
190             updateState();
191
192             // kick off update states
193             postUpdate();
194         } else {
195             // otherwise remove our update
196             removeUpdate();
197         }
198     }
199
200     /**
201      * Updates the state of the progress bar and the play pause button
202      */
203     private void updateState() {
204         mCurrentSongDuration = MusicUtils.duration();
205         mCurrentSongProgress = MusicUtils.position();
206
207         int progress = 0;
208         if (isDragging()) {
209             progress = (int) (mDragPercentage * mProgressBar.getMax());
210         } else if (mCurrentSongDuration > 0) {
211             progress = (int) (mProgressBar.getMax() * mCurrentSongProgress / mCurrentSongDuration);
212         }
213
214         mProgressBar.setProgress(progress);
215         mPlayPauseButton.updateState();
216     }
217
218     /**
219      * Creates and posts the update runnable to the handler
220      */
221     private void postUpdate() {
222         if (mUpdateProgress == null) {
223             mUpdateProgress = new Runnable() {
224                 @Override
225                 public void run() {
226                     updateState();
227                     postDelayed(mUpdateProgress, isDragging() ? MusicUtils.UPDATE_FREQUENCY_FAST_MS
228                             : MusicUtils.UPDATE_FREQUENCY_MS);
229                 }
230             };
231         }
232
233         // remove any existing callbacks
234         removeCallbacks(mUpdateProgress);
235
236         // post ourselves as a delayed
237         post(mUpdateProgress);
238     }
239
240     /**
241      * Removes the runnable from the handler
242      */
243     private void removeUpdate() {
244         if (mUpdateProgress != null) {
245             removeCallbacks(mUpdateProgress);
246         }
247     }
248
249     @Override
250     protected void onSizeChanged(int w, int h, int oldW, int oldH) {
251         mWidth = Math.min(w, h);
252     }
253
254     @Override
255     public boolean onInterceptTouchEvent(MotionEvent ev) {
256         if (!mDragEnabled) {
257             return false;
258         }
259
260         return onTouchEvent(ev);
261     }
262
263     @Override
264     public boolean onTouchEvent(MotionEvent event) {
265         final float x = event.getX();
266         final float y = event.getY();
267
268         if (!mDragEnabled || mCurrentSongDuration <= 0) {
269             return false;
270         }
271
272         switch (event.getActionMasked()) {
273             case MotionEvent.ACTION_DOWN:
274                 mDownX = event.getX();
275                 mDownY = event.getY();
276                 mDownAngle = angle(mDownX, mDownY);
277                 mDragAngle = REVOLUTION_IN_DEGREES
278                         * (mCurrentSongProgress / (float) mCurrentSongDuration);
279                 mDragPercentage = mDragAngle / REVOLUTION_IN_DEGREES;
280                 mDragging = false;
281                 break;
282             case MotionEvent.ACTION_MOVE:
283                 // if the user has moved a certain distance
284                 if (Math.sqrt(Math.pow(event.getX() - mDownX, 2)
285                         + Math.pow(event.getY() - mDownY, 2)) < mSmallDistance) {
286                     return false;
287                 }
288
289                 // if we weren't previously dragging, immediately kick off an update to reflect
290                 // the change faster
291                 if (!mDragging) {
292                     postUpdate();
293                 }
294
295                 mDragging = true;
296                 getParent().requestDisallowInterceptTouchEvent(true);
297
298                 // calculate the amount of angle we've moved
299                 final float deltaAngle = getDelta(x, y);
300                 mDragAngle = cropAngle(mDragAngle + deltaAngle);
301                 mDragPercentage = mDragAngle / REVOLUTION_IN_DEGREES;
302
303                 if (DEBUG) {
304                     Log.d(TAG, "Delta Angle: " + deltaAngle + ", Target Angle: " + mDownAngle);
305                 }
306
307                 return true;
308             case MotionEvent.ACTION_UP:
309             case MotionEvent.ACTION_CANCEL:
310                 // if we were dragging, seek to where we dragged to
311                 if (mDragging) {
312                     MusicUtils.seek((long)(mDragPercentage * mCurrentSongDuration));
313                 }
314                 mDragging = false;
315             default:
316                 break;
317         }
318         return mDragging;
319     }
320
321     /**
322      * Crops the angle between 0 and 360 - if the angle is < 0, it will return 0, if it is more than
323      * 360 it will return 360
324      */
325     private static float cropAngle(float angle) {
326         return Math.min(REVOLUTION_IN_DEGREES, Math.max(0.0f, angle));
327     }
328
329     /**
330      * Wraps the angle between -180 and 180. This assumes that the passed in
331      * angle is >= -360 and <= 360
332      */
333     private static float wrapHalfRevolution(float angle) {
334         if (angle < -HALF_REVOLUTION_IN_DEGREES) {
335             return angle + REVOLUTION_IN_DEGREES;
336         } else if (angle > HALF_REVOLUTION_IN_DEGREES) {
337             return angle - REVOLUTION_IN_DEGREES;
338         }
339
340         return angle;
341     }
342
343     /**
344      * Gets the change in angle from the down angle and updates the down angle to the current angle
345      */
346     private float getDelta(float x, float y) {
347         float angle = angle(x, y);
348         float deltaAngle = wrapHalfRevolution(angle - mDownAngle);
349         mDownAngle = angle;
350         return deltaAngle;
351     }
352
353     /**
354      * Calculates the angle at the point passed in based on the center of the button
355      */
356     private float angle(float x, float y) {
357         float center = mWidth / 2.0f;
358         x -= center;
359         y -= center;
360
361         if (x == 0.0f) {
362             if (y > 0.0f) {
363                 return 180.0f;
364             } else {
365                 return 0.0f;
366             }
367         }
368
369         float angle = (float) (Math.atan(y / x) / Math.PI * 180.0);
370         if (x > 0.0f) {
371             angle += 90;
372         } else {
373             angle += 270;
374         }
375         return angle;
376     }
377 }