* commit '310796de0b86074229ad804001b1ef8d466ee69c': Add a scrubber to keyguard; layout tweaks
This commit is contained in:
@@ -13356,6 +13356,7 @@ package android.media {
|
|||||||
ctor public RemoteController(android.content.Context, android.os.Looper) throws java.lang.IllegalArgumentException;
|
ctor public RemoteController(android.content.Context, android.os.Looper) throws java.lang.IllegalArgumentException;
|
||||||
method public int clearArtworkConfiguration();
|
method public int clearArtworkConfiguration();
|
||||||
method public android.media.RemoteController.MetadataEditor editMetadata();
|
method public android.media.RemoteController.MetadataEditor editMetadata();
|
||||||
|
method public long getEstimatedMediaPosition();
|
||||||
method public int seekTo(long);
|
method public int seekTo(long);
|
||||||
method public int sendMediaKeyEvent(android.view.KeyEvent);
|
method public int sendMediaKeyEvent(android.view.KeyEvent);
|
||||||
method public int setArtworkConfiguration(int, int);
|
method public int setArtworkConfiguration(int, int);
|
||||||
|
|||||||
@@ -1674,7 +1674,7 @@ public class RemoteControlClient
|
|||||||
* @return true during any form of playback, false if it's not playing anything while in this
|
* @return true during any form of playback, false if it's not playing anything while in this
|
||||||
* playback state
|
* playback state
|
||||||
*/
|
*/
|
||||||
private static boolean playbackPositionShouldMove(int playstate) {
|
static boolean playbackPositionShouldMove(int playstate) {
|
||||||
switch(playstate) {
|
switch(playstate) {
|
||||||
case PLAYSTATE_STOPPED:
|
case PLAYSTATE_STOPPED:
|
||||||
case PLAYSTATE_PAUSED:
|
case PLAYSTATE_PAUSED:
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package android.media;
|
package android.media;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.app.ActivityManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.PendingIntent.CanceledException;
|
import android.app.PendingIntent.CanceledException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -30,6 +31,8 @@ import android.os.Looper;
|
|||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.ServiceManager;
|
import android.os.ServiceManager;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
@@ -59,6 +62,7 @@ public final class RemoteController
|
|||||||
private final RcDisplay mRcd;
|
private final RcDisplay mRcd;
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final AudioManager mAudioManager;
|
private final AudioManager mAudioManager;
|
||||||
|
private final int mMaxBitmapDimension;
|
||||||
private MetadataEditor mMetadataEditor;
|
private MetadataEditor mMetadataEditor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,6 +114,13 @@ public final class RemoteController
|
|||||||
mContext = context;
|
mContext = context;
|
||||||
mRcd = new RcDisplay();
|
mRcd = new RcDisplay();
|
||||||
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
|
||||||
|
if (ActivityManager.isLowRamDeviceStatic()) {
|
||||||
|
mMaxBitmapDimension = MAX_BITMAP_DIMENSION;
|
||||||
|
} else {
|
||||||
|
final DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||||
|
mMaxBitmapDimension = Math.max(dm.widthPixels, dm.heightPixels);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -142,7 +153,7 @@ public final class RemoteController
|
|||||||
* @param state one of the playback states authorized
|
* @param state one of the playback states authorized
|
||||||
* in {@link RemoteControlClient#setPlaybackState(int)}.
|
* in {@link RemoteControlClient#setPlaybackState(int)}.
|
||||||
* @param stateChangeTimeMs the system time at which the state change was reported,
|
* @param stateChangeTimeMs the system time at which the state change was reported,
|
||||||
* expressed in ms.
|
* expressed in ms. Based on {@link android.os.SystemClock.elapsedRealtime()}.
|
||||||
* @param currentPosMs a positive value for the current media playback position expressed
|
* @param currentPosMs a positive value for the current media playback position expressed
|
||||||
* in ms, a negative value if the position is temporarily unknown.
|
* in ms, a negative value if the position is temporarily unknown.
|
||||||
* @param speed a value expressed as a ratio of 1x playback: 1.0f is normal playback,
|
* @param speed a value expressed as a ratio of 1x playback: 1.0f is normal playback,
|
||||||
@@ -200,6 +211,50 @@ public final class RemoteController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public String getRemoteControlClientPackageName() {
|
||||||
|
return mClientPendingIntentCurrent != null ?
|
||||||
|
mClientPendingIntentCurrent.getCreatorPackage() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the estimated playback position of the current media track or a negative value
|
||||||
|
* if not available.
|
||||||
|
*
|
||||||
|
* <p>The value returned is estimated by the current process and may not be perfect.
|
||||||
|
* The time returned by this method is calculated from the last state change time based
|
||||||
|
* on the current play position at that time and the last known playback speed.
|
||||||
|
* An application may call {@link #setSynchronizationMode(int)} to apply
|
||||||
|
* a synchronization policy that will periodically re-sync the estimated position
|
||||||
|
* with the RemoteControlClient.</p>
|
||||||
|
*
|
||||||
|
* @return the current estimated playback position in milliseconds or a negative value
|
||||||
|
* if not available
|
||||||
|
*
|
||||||
|
* @see OnClientUpdateListener#onClientPlaybackStateUpdate(int, long, long, float)
|
||||||
|
*/
|
||||||
|
public long getEstimatedMediaPosition() {
|
||||||
|
if (mLastPlaybackInfo != null) {
|
||||||
|
if (!RemoteControlClient.playbackPositionShouldMove(mLastPlaybackInfo.mState)) {
|
||||||
|
return mLastPlaybackInfo.mCurrentPosMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take the current position at the time of state change and estimate.
|
||||||
|
final long thenPos = mLastPlaybackInfo.mCurrentPosMs;
|
||||||
|
if (thenPos < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final long now = SystemClock.elapsedRealtime();
|
||||||
|
final long then = mLastPlaybackInfo.mStateChangeTimeMs;
|
||||||
|
final long sinceThen = now - then;
|
||||||
|
final long scaledSinceThen = (long) (sinceThen * mLastPlaybackInfo.mSpeed);
|
||||||
|
return thenPos + scaledSinceThen;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a simulated key event for a media button to be received by the current client.
|
* Send a simulated key event for a media button to be received by the current client.
|
||||||
@@ -301,8 +356,8 @@ public final class RemoteController
|
|||||||
synchronized (mInfoLock) {
|
synchronized (mInfoLock) {
|
||||||
if (wantBitmap) {
|
if (wantBitmap) {
|
||||||
if ((width > 0) && (height > 0)) {
|
if ((width > 0) && (height > 0)) {
|
||||||
if (width > MAX_BITMAP_DIMENSION) { width = MAX_BITMAP_DIMENSION; }
|
if (width > mMaxBitmapDimension) { width = mMaxBitmapDimension; }
|
||||||
if (height > MAX_BITMAP_DIMENSION) { height = MAX_BITMAP_DIMENSION; }
|
if (height > mMaxBitmapDimension) { height = mMaxBitmapDimension; }
|
||||||
mArtworkWidth = width;
|
mArtworkWidth = width;
|
||||||
mArtworkHeight = height;
|
mArtworkHeight = height;
|
||||||
} else {
|
} else {
|
||||||
@@ -415,7 +470,13 @@ public final class RemoteController
|
|||||||
protected MetadataEditor(Bundle metadata, long editableKeys) {
|
protected MetadataEditor(Bundle metadata, long editableKeys) {
|
||||||
mEditorMetadata = metadata;
|
mEditorMetadata = metadata;
|
||||||
mEditableKeys = editableKeys;
|
mEditableKeys = editableKeys;
|
||||||
mEditorArtwork = null;
|
|
||||||
|
mEditorArtwork = (Bitmap) metadata.getParcelable(
|
||||||
|
String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK));
|
||||||
|
if (mEditorArtwork != null) {
|
||||||
|
cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK);
|
||||||
|
}
|
||||||
|
|
||||||
mMetadataChanged = true;
|
mMetadataChanged = true;
|
||||||
mArtworkChanged = true;
|
mArtworkChanged = true;
|
||||||
mApplied = false;
|
mApplied = false;
|
||||||
@@ -706,6 +767,7 @@ public final class RemoteController
|
|||||||
// existing metadata, merge existing and new
|
// existing metadata, merge existing and new
|
||||||
mMetadataEditor.mEditorMetadata.putAll(metadata);
|
mMetadataEditor.mEditorMetadata.putAll(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
mMetadataEditor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK,
|
mMetadataEditor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK,
|
||||||
(Bitmap)metadata.getParcelable(
|
(Bitmap)metadata.getParcelable(
|
||||||
String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK)));
|
String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK)));
|
||||||
|
|||||||
@@ -22,34 +22,133 @@
|
|||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:id="@+id/keyguard_transport_control">
|
android:id="@+id/keyguard_transport_control">
|
||||||
|
|
||||||
<!-- Use ImageView for its cropping features; otherwise could be android:background -->
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/albumart"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="fill"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
android:adjustViewBounds="false"
|
|
||||||
android:contentDescription="@string/keygaurd_accessibility_media_controls" />
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom">
|
android:layout_gravity="top"
|
||||||
<TextView
|
android:gravity="center">
|
||||||
android:id="@+id/title"
|
<ImageView
|
||||||
|
android:id="@+id/badge"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:scaleType="fitCenter" />
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/info_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:layout_marginTop="8dip"
|
<LinearLayout
|
||||||
android:layout_marginStart="16dip"
|
android:id="@+id/metadata_container"
|
||||||
android:layout_marginEnd="16dip"
|
android:orientation="vertical"
|
||||||
android:gravity="center_horizontal"
|
android:layout_width="match_parent"
|
||||||
android:singleLine="true"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="end"
|
android:layout_gravity="center">
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
<TextView
|
||||||
/>
|
android:id="@+id/title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dip"
|
||||||
|
android:layout_marginEnd="16dip"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:fontFamily="sans-serif-light" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/artist_album"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dip"
|
||||||
|
android:layout_marginEnd="16dip"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="?android:attr/textColorSecondary" />
|
||||||
|
</LinearLayout>
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/transient_seek"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="invisible">
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/transient_seek_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/transient_seek_time_elapsed"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_below="@id/transient_seek_bar"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textSize="12dp" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/transient_seek_time_remaining"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_below="@id/transient_seek_bar"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textSize="12dp" />
|
||||||
|
</RelativeLayout>
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/transient_rating"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="invisible">
|
||||||
|
<RatingBar
|
||||||
|
android:id="@+id/transient_rating_bar_stars"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/transient_rating_thumbs"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1">
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_thumbs_up"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/ic_media_previous"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:contentDescription="@string/keyguard_accessibility_transport_thumbs_up_description"/>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1">
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_thumbs_down"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/ic_media_next"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:contentDescription="@string/keyguard_accessibility_transport_thumbs_down_description"/>
|
||||||
|
</FrameLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
<ToggleButton
|
||||||
|
android:id="@+id/transient_rating_heart"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="invisible"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:contentDescription="@string/keyguard_accessibility_transport_heart_description" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -59,45 +158,45 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
<ImageView
|
<ImageButton
|
||||||
android:id="@+id/btn_prev"
|
android:id="@+id/btn_prev"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:src="@drawable/ic_media_previous"
|
android:src="@drawable/ic_media_previous"
|
||||||
android:clickable="true"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:padding="10dip"
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
android:contentDescription="@string/keyguard_accessibility_transport_prev_description"/>
|
android:contentDescription="@string/keyguard_accessibility_transport_prev_description"/>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
<ImageView
|
<ImageButton
|
||||||
android:id="@+id/btn_play"
|
android:id="@+id/btn_play"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:clickable="true"
|
|
||||||
android:src="@drawable/ic_media_play"
|
android:src="@drawable/ic_media_play"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:padding="10dip"
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
android:contentDescription="@string/keyguard_accessibility_transport_play_description"/>
|
android:contentDescription="@string/keyguard_accessibility_transport_play_description"/>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
<ImageView
|
<ImageButton
|
||||||
android:id="@+id/btn_next"
|
android:id="@+id/btn_next"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:clickable="true"
|
|
||||||
android:src="@drawable/ic_media_next"
|
android:src="@drawable/ic_media_next"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:padding="10dip"
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
android:contentDescription="@string/keyguard_accessibility_transport_next_description"/>
|
android:contentDescription="@string/keyguard_accessibility_transport_next_description"/>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -152,6 +152,13 @@
|
|||||||
<string name="keyguard_accessibility_transport_play_description">Play button</string>
|
<string name="keyguard_accessibility_transport_play_description">Play button</string>
|
||||||
<!-- Shown on transport control of lockscreen. Pressing button pauses playback -->
|
<!-- Shown on transport control of lockscreen. Pressing button pauses playback -->
|
||||||
<string name="keyguard_accessibility_transport_stop_description">Stop button</string>
|
<string name="keyguard_accessibility_transport_stop_description">Stop button</string>
|
||||||
|
<!-- Shown on transport control of lockscreen. Pressing button rates the track as "thumbs up." -->
|
||||||
|
<string name="keyguard_accessibility_transport_thumbs_up_description">Thumbs up</string>
|
||||||
|
<!-- Shown on transport control of lockscreen. Pressing button rates the track as "thumbs down." -->
|
||||||
|
<string name="keyguard_accessibility_transport_thumbs_down_description">Thumbs down</string>
|
||||||
|
<!-- Shown on transport control of lockscreen. Pressing button toggles the "heart" rating. -->
|
||||||
|
<string name="keyguard_accessibility_transport_heart_description">Heart</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Accessibility description for when the device prompts the user to dismiss keyguard
|
<!-- Accessibility description for when the device prompts the user to dismiss keyguard
|
||||||
in order to complete an action. This will be followed by a message about the current
|
in order to complete an action. This will be followed by a message about the current
|
||||||
|
|||||||
@@ -134,6 +134,10 @@ public class KeyguardHostView extends KeyguardViewBase {
|
|||||||
void userActivity();
|
void userActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TransportControlCallback {
|
||||||
|
void userActivity();
|
||||||
|
}
|
||||||
|
|
||||||
/*package*/ interface OnDismissAction {
|
/*package*/ interface OnDismissAction {
|
||||||
/* returns true if the dismiss should be deferred */
|
/* returns true if the dismiss should be deferred */
|
||||||
boolean onDismiss();
|
boolean onDismiss();
|
||||||
@@ -1222,6 +1226,11 @@ public class KeyguardHostView extends KeyguardViewBase {
|
|||||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||||
mTransportControl = (KeyguardTransportControlView)
|
mTransportControl = (KeyguardTransportControlView)
|
||||||
inflater.inflate(R.layout.keyguard_transport_control_view, this, false);
|
inflater.inflate(R.layout.keyguard_transport_control_view, this, false);
|
||||||
|
mTransportControl.setTransportControlCallback(new TransportControlCallback() {
|
||||||
|
public void userActivity() {
|
||||||
|
mViewMediatorCallback.userActivity();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return mTransportControl;
|
return mTransportControl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,191 +16,263 @@
|
|||||||
|
|
||||||
package com.android.keyguard;
|
package com.android.keyguard;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.PendingIntent.CanceledException;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.ColorMatrix;
|
||||||
|
import android.graphics.ColorMatrixColorFilter;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.IRemoteControlDisplay;
|
import android.media.MediaMetadataEditor;
|
||||||
import android.media.MediaMetadataRetriever;
|
import android.media.MediaMetadataRetriever;
|
||||||
import android.media.RemoteControlClient;
|
import android.media.RemoteControlClient;
|
||||||
import android.os.Bundle;
|
import android.media.RemoteController;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.format.DateFormat;
|
||||||
|
import android.transition.ChangeBounds;
|
||||||
|
import android.transition.ChangeText;
|
||||||
|
import android.transition.Fade;
|
||||||
|
import android.transition.TransitionManager;
|
||||||
|
import android.transition.TransitionSet;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.SeekBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the widget responsible for showing music controls in keyguard.
|
* This is the widget responsible for showing music controls in keyguard.
|
||||||
*/
|
*/
|
||||||
public class KeyguardTransportControlView extends FrameLayout implements OnClickListener {
|
public class KeyguardTransportControlView extends FrameLayout {
|
||||||
|
|
||||||
private static final int MSG_UPDATE_STATE = 100;
|
|
||||||
private static final int MSG_SET_METADATA = 101;
|
|
||||||
private static final int MSG_SET_TRANSPORT_CONTROLS = 102;
|
|
||||||
private static final int MSG_SET_ARTWORK = 103;
|
|
||||||
private static final int MSG_SET_GENERATION_ID = 104;
|
|
||||||
private static final int DISPLAY_TIMEOUT_MS = 5000; // 5s
|
private static final int DISPLAY_TIMEOUT_MS = 5000; // 5s
|
||||||
|
private static final int RESET_TO_METADATA_DELAY = 5000;
|
||||||
protected static final boolean DEBUG = false;
|
protected static final boolean DEBUG = false;
|
||||||
protected static final String TAG = "TransportControlView";
|
protected static final String TAG = "TransportControlView";
|
||||||
|
|
||||||
private ImageView mAlbumArt;
|
private static final boolean ANIMATE_TRANSITIONS = false;
|
||||||
|
|
||||||
|
private ViewGroup mMetadataContainer;
|
||||||
|
private ViewGroup mInfoContainer;
|
||||||
private TextView mTrackTitle;
|
private TextView mTrackTitle;
|
||||||
|
private TextView mTrackArtistAlbum;
|
||||||
|
|
||||||
|
private View mTransientSeek;
|
||||||
|
private SeekBar mTransientSeekBar;
|
||||||
|
private TextView mTransientSeekTimeElapsed;
|
||||||
|
private TextView mTransientSeekTimeRemaining;
|
||||||
|
|
||||||
private ImageView mBtnPrev;
|
private ImageView mBtnPrev;
|
||||||
private ImageView mBtnPlay;
|
private ImageView mBtnPlay;
|
||||||
private ImageView mBtnNext;
|
private ImageView mBtnNext;
|
||||||
private int mClientGeneration;
|
|
||||||
private Metadata mMetadata = new Metadata();
|
private Metadata mMetadata = new Metadata();
|
||||||
private boolean mAttached;
|
|
||||||
private PendingIntent mClientIntent;
|
|
||||||
private int mTransportControlFlags;
|
private int mTransportControlFlags;
|
||||||
private int mCurrentPlayState;
|
private int mCurrentPlayState;
|
||||||
private AudioManager mAudioManager;
|
private AudioManager mAudioManager;
|
||||||
private IRemoteControlDisplayWeak mIRCD;
|
private RemoteController mRemoteController;
|
||||||
|
|
||||||
|
private ImageView mBadge;
|
||||||
|
|
||||||
|
private boolean mSeekEnabled;
|
||||||
|
private boolean mUserSeeking;
|
||||||
|
private java.text.DateFormat mFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The metadata which should be populated into the view once we've been attached
|
* The metadata which should be populated into the view once we've been attached
|
||||||
*/
|
*/
|
||||||
private Bundle mPopulateMetadataWhenAttached = null;
|
private RemoteController.MetadataEditor mPopulateMetadataWhenAttached = null;
|
||||||
|
|
||||||
// This handler is required to ensure messages from IRCD are handled in sequence and on
|
private RemoteController.OnClientUpdateListener mRCClientUpdateListener =
|
||||||
// the UI thread.
|
new RemoteController.OnClientUpdateListener() {
|
||||||
private Handler mHandler = new Handler() {
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message msg) {
|
public void onClientChange(boolean clearing) {
|
||||||
switch (msg.what) {
|
if (clearing) {
|
||||||
case MSG_UPDATE_STATE:
|
clearMetadata();
|
||||||
if (mClientGeneration == msg.arg1) updatePlayPauseState(msg.arg2);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MSG_SET_METADATA:
|
|
||||||
if (mClientGeneration == msg.arg1) updateMetadata((Bundle) msg.obj);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MSG_SET_TRANSPORT_CONTROLS:
|
|
||||||
if (mClientGeneration == msg.arg1) updateTransportControls(msg.arg2);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MSG_SET_ARTWORK:
|
|
||||||
if (mClientGeneration == msg.arg1) {
|
|
||||||
mMetadata.bitmap = (Bitmap) msg.obj;
|
|
||||||
KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(
|
|
||||||
mMetadata.bitmap);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MSG_SET_GENERATION_ID:
|
|
||||||
if (DEBUG) Log.v(TAG, "New genId = " + msg.arg1 + ", clearing = " + msg.arg2);
|
|
||||||
mClientGeneration = msg.arg1;
|
|
||||||
mClientIntent = (PendingIntent) msg.obj;
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* This class is required to have weak linkage to the current TransportControlView
|
public void onClientPlaybackStateUpdate(int state) {
|
||||||
* because the remote process can hold a strong reference to this binder object and
|
setSeekBarsEnabled(false);
|
||||||
* we can't predict when it will be GC'd in the remote process. Without this code, it
|
updatePlayPauseState(state);
|
||||||
* would allow a heavyweight object to be held on this side of the binder when there's
|
|
||||||
* no requirement to run a GC on the other side.
|
|
||||||
*/
|
|
||||||
private static class IRemoteControlDisplayWeak extends IRemoteControlDisplay.Stub {
|
|
||||||
private WeakReference<Handler> mLocalHandler;
|
|
||||||
|
|
||||||
IRemoteControlDisplayWeak(Handler handler) {
|
|
||||||
mLocalHandler = new WeakReference<Handler>(handler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
|
@Override
|
||||||
|
public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
|
||||||
long currentPosMs, float speed) {
|
long currentPosMs, float speed) {
|
||||||
Handler handler = mLocalHandler.get();
|
setSeekBarsEnabled(mMetadata != null && mMetadata.duration > 0);
|
||||||
if (handler != null) {
|
updatePlayPauseState(state);
|
||||||
handler.obtainMessage(MSG_UPDATE_STATE, generationId, state).sendToTarget();
|
if (DEBUG) Log.d(TAG, "onClientPlaybackStateUpdate(state=" + state +
|
||||||
}
|
", stateChangeTimeMs=" + stateChangeTimeMs + ", currentPosMs=" + currentPosMs +
|
||||||
|
", speed=" + speed + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMetadata(int generationId, Bundle metadata) {
|
@Override
|
||||||
Handler handler = mLocalHandler.get();
|
public void onClientTransportControlUpdate(int transportControlFlags) {
|
||||||
if (handler != null) {
|
updateTransportControls(transportControlFlags);
|
||||||
handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTransportControlInfo(int generationId, int flags, int posCapabilities) {
|
@Override
|
||||||
Handler handler = mLocalHandler.get();
|
public void onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor) {
|
||||||
if (handler != null) {
|
updateMetadata(metadataEditor);
|
||||||
handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags)
|
|
||||||
.sendToTarget();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public void setArtwork(int generationId, Bitmap bitmap) {
|
private final Runnable mUpdateSeekBars = new Runnable() {
|
||||||
Handler handler = mLocalHandler.get();
|
public void run() {
|
||||||
if (handler != null) {
|
if (updateSeekBars()) {
|
||||||
handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget();
|
postDelayed(this, 1000);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) {
|
|
||||||
Handler handler = mLocalHandler.get();
|
|
||||||
if (handler != null) {
|
|
||||||
handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
|
|
||||||
handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
|
|
||||||
boolean clearing) throws RemoteException {
|
|
||||||
Handler handler = mLocalHandler.get();
|
|
||||||
if (handler != null) {
|
|
||||||
handler.obtainMessage(MSG_SET_GENERATION_ID,
|
|
||||||
clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private final Runnable mResetToMetadata = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
resetToMetadata();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final OnClickListener mTransportCommandListener = new OnClickListener() {
|
||||||
|
public void onClick(View v) {
|
||||||
|
int keyCode = -1;
|
||||||
|
if (v == mBtnPrev) {
|
||||||
|
keyCode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
|
||||||
|
} else if (v == mBtnNext) {
|
||||||
|
keyCode = KeyEvent.KEYCODE_MEDIA_NEXT;
|
||||||
|
} else if (v == mBtnPlay) {
|
||||||
|
keyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
|
||||||
|
}
|
||||||
|
if (keyCode != -1) {
|
||||||
|
sendMediaButtonClick(keyCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final OnLongClickListener mTransportShowSeekBarListener = new OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
if (mSeekEnabled) {
|
||||||
|
return tryToggleSeekBar();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener =
|
||||||
|
new SeekBar.OnSeekBarChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
|
if (fromUser) {
|
||||||
|
scrubTo(progress);
|
||||||
|
delayResetToMetadata();
|
||||||
|
}
|
||||||
|
updateSeekDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
mUserSeeking = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
|
mUserSeeking = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final int TRANSITION_DURATION = 200;
|
||||||
|
private final TransitionSet mMetadataChangeTransition;
|
||||||
|
|
||||||
|
KeyguardHostView.TransportControlCallback mTransportControlCallback;
|
||||||
|
|
||||||
public KeyguardTransportControlView(Context context, AttributeSet attrs) {
|
public KeyguardTransportControlView(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
if (DEBUG) Log.v(TAG, "Create TCV " + this);
|
if (DEBUG) Log.v(TAG, "Create TCV " + this);
|
||||||
mAudioManager = new AudioManager(mContext);
|
mAudioManager = new AudioManager(mContext);
|
||||||
mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
|
mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
|
||||||
mIRCD = new IRemoteControlDisplayWeak(mHandler);
|
mRemoteController = new RemoteController(context);
|
||||||
|
mRemoteController.setOnClientUpdateListener(mRCClientUpdateListener);
|
||||||
|
|
||||||
|
final DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||||
|
final int dim = Math.max(dm.widthPixels, dm.heightPixels);
|
||||||
|
mRemoteController.setArtworkConfiguration(true, dim, dim);
|
||||||
|
|
||||||
|
final ChangeText tc = new ChangeText();
|
||||||
|
tc.setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN);
|
||||||
|
final TransitionSet inner = new TransitionSet();
|
||||||
|
inner.addTransition(tc).addTransition(new ChangeBounds());
|
||||||
|
final TransitionSet tg = new TransitionSet();
|
||||||
|
tg.addTransition(new Fade(Fade.OUT)).addTransition(inner).
|
||||||
|
addTransition(new Fade(Fade.IN));
|
||||||
|
tg.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
|
||||||
|
tg.setDuration(TRANSITION_DURATION);
|
||||||
|
mMetadataChangeTransition = tg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTransportControls(int transportControlFlags) {
|
private void updateTransportControls(int transportControlFlags) {
|
||||||
mTransportControlFlags = transportControlFlags;
|
mTransportControlFlags = transportControlFlags;
|
||||||
|
setSeekBarsEnabled(
|
||||||
|
(transportControlFlags & RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSeekBarsEnabled(boolean enabled) {
|
||||||
|
if (enabled == mSeekEnabled) return;
|
||||||
|
|
||||||
|
mSeekEnabled = enabled;
|
||||||
|
if (mTransientSeek.getVisibility() == VISIBLE) {
|
||||||
|
mTransientSeek.setVisibility(INVISIBLE);
|
||||||
|
mMetadataContainer.setVisibility(VISIBLE);
|
||||||
|
mUserSeeking = false;
|
||||||
|
cancelResetToMetadata();
|
||||||
|
}
|
||||||
|
if (enabled) {
|
||||||
|
mUpdateSeekBars.run();
|
||||||
|
postDelayed(mUpdateSeekBars, 1000);
|
||||||
|
} else {
|
||||||
|
removeCallbacks(mUpdateSeekBars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransportControlCallback(KeyguardHostView.TransportControlCallback
|
||||||
|
transportControlCallback) {
|
||||||
|
mTransportControlCallback = transportControlCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinishInflate() {
|
public void onFinishInflate() {
|
||||||
super.onFinishInflate();
|
super.onFinishInflate();
|
||||||
|
mInfoContainer = (ViewGroup) findViewById(R.id.info_container);
|
||||||
|
mMetadataContainer = (ViewGroup) findViewById(R.id.metadata_container);
|
||||||
|
mBadge = (ImageView) findViewById(R.id.badge);
|
||||||
mTrackTitle = (TextView) findViewById(R.id.title);
|
mTrackTitle = (TextView) findViewById(R.id.title);
|
||||||
mTrackTitle.setSelected(true); // enable marquee
|
mTrackTitle.setSelected(true); // enable marquee
|
||||||
mAlbumArt = (ImageView) findViewById(R.id.albumart);
|
mTrackArtistAlbum = (TextView) findViewById(R.id.artist_album);
|
||||||
|
mTrackArtistAlbum.setSelected(true);
|
||||||
|
mTransientSeek = findViewById(R.id.transient_seek);
|
||||||
|
mTransientSeekBar = (SeekBar) findViewById(R.id.transient_seek_bar);
|
||||||
|
mTransientSeekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
|
||||||
|
mTransientSeekTimeElapsed = (TextView) findViewById(R.id.transient_seek_time_elapsed);
|
||||||
|
mTransientSeekTimeRemaining = (TextView) findViewById(R.id.transient_seek_time_remaining);
|
||||||
mBtnPrev = (ImageView) findViewById(R.id.btn_prev);
|
mBtnPrev = (ImageView) findViewById(R.id.btn_prev);
|
||||||
mBtnPlay = (ImageView) findViewById(R.id.btn_play);
|
mBtnPlay = (ImageView) findViewById(R.id.btn_play);
|
||||||
mBtnNext = (ImageView) findViewById(R.id.btn_next);
|
mBtnNext = (ImageView) findViewById(R.id.btn_next);
|
||||||
final View buttons[] = { mBtnPrev, mBtnPlay, mBtnNext };
|
final View buttons[] = { mBtnPrev, mBtnPlay, mBtnNext };
|
||||||
for (View view : buttons) {
|
for (View view : buttons) {
|
||||||
view.setOnClickListener(this);
|
view.setOnClickListener(mTransportCommandListener);
|
||||||
|
view.setOnLongClickListener(mTransportShowSeekBarListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,32 +284,34 @@ public class KeyguardTransportControlView extends FrameLayout implements OnClick
|
|||||||
updateMetadata(mPopulateMetadataWhenAttached);
|
updateMetadata(mPopulateMetadataWhenAttached);
|
||||||
mPopulateMetadataWhenAttached = null;
|
mPopulateMetadataWhenAttached = null;
|
||||||
}
|
}
|
||||||
if (!mAttached) {
|
if (DEBUG) Log.v(TAG, "Registering TCV " + this);
|
||||||
if (DEBUG) Log.v(TAG, "Registering TCV " + this);
|
mAudioManager.registerRemoteController(mRemoteController);
|
||||||
mAudioManager.registerRemoteControlDisplay(mIRCD);
|
|
||||||
}
|
|
||||||
mAttached = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
|
protected void onConfigurationChanged(Configuration newConfig) {
|
||||||
if (mAttached) {
|
super.onConfigurationChanged(newConfig);
|
||||||
final DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
|
final DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
|
||||||
int dim = Math.max(dm.widthPixels, dm.heightPixels);
|
final int dim = Math.max(dm.widthPixels, dm.heightPixels);
|
||||||
if (DEBUG) Log.v(TAG, "TCV uses bitmap size=" + dim);
|
mRemoteController.setArtworkConfiguration(true, dim, dim);
|
||||||
mAudioManager.remoteControlDisplayUsesBitmapSize(mIRCD, dim, dim);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDetachedFromWindow() {
|
public void onDetachedFromWindow() {
|
||||||
if (DEBUG) Log.v(TAG, "onDetachFromWindow()");
|
if (DEBUG) Log.v(TAG, "onDetachFromWindow()");
|
||||||
super.onDetachedFromWindow();
|
super.onDetachedFromWindow();
|
||||||
if (mAttached) {
|
if (DEBUG) Log.v(TAG, "Unregistering TCV " + this);
|
||||||
if (DEBUG) Log.v(TAG, "Unregistering TCV " + this);
|
mAudioManager.unregisterRemoteController(mRemoteController);
|
||||||
mAudioManager.unregisterRemoteControlDisplay(mIRCD);
|
mUserSeeking = false;
|
||||||
}
|
}
|
||||||
mAttached = false;
|
|
||||||
|
void setBadgeIcon(Drawable bmp) {
|
||||||
|
mBadge.setImageDrawable(bmp);
|
||||||
|
|
||||||
|
final ColorMatrix cm = new ColorMatrix();
|
||||||
|
cm.setSaturation(0);
|
||||||
|
mBadge.setColorFilter(new ColorMatrixColorFilter(cm));
|
||||||
|
mBadge.setImageAlpha(0xef);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Metadata {
|
class Metadata {
|
||||||
@@ -245,21 +319,39 @@ public class KeyguardTransportControlView extends FrameLayout implements OnClick
|
|||||||
private String trackTitle;
|
private String trackTitle;
|
||||||
private String albumTitle;
|
private String albumTitle;
|
||||||
private Bitmap bitmap;
|
private Bitmap bitmap;
|
||||||
|
private long duration;
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
artist = null;
|
||||||
|
trackTitle = null;
|
||||||
|
albumTitle = null;
|
||||||
|
bitmap = null;
|
||||||
|
duration = -1;
|
||||||
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + " albumTitle=" + albumTitle + "]";
|
return "Metadata[artist=" + artist + " trackTitle=" + trackTitle +
|
||||||
|
" albumTitle=" + albumTitle + " duration=" + duration + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getMdString(Bundle data, int id) {
|
void clearMetadata() {
|
||||||
return data.getString(Integer.toString(id));
|
mPopulateMetadataWhenAttached = null;
|
||||||
|
mMetadata.clear();
|
||||||
|
populateMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMetadata(Bundle data) {
|
void updateMetadata(RemoteController.MetadataEditor data) {
|
||||||
if (mAttached) {
|
if (isAttachedToWindow()) {
|
||||||
mMetadata.artist = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
|
mMetadata.artist = data.getString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
|
||||||
mMetadata.trackTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_TITLE);
|
mMetadata.artist);
|
||||||
mMetadata.albumTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUM);
|
mMetadata.trackTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_TITLE,
|
||||||
|
mMetadata.trackTitle);
|
||||||
|
mMetadata.albumTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_ALBUM,
|
||||||
|
mMetadata.albumTitle);
|
||||||
|
mMetadata.duration = data.getLong(MediaMetadataRetriever.METADATA_KEY_DURATION, -1);
|
||||||
|
mMetadata.bitmap = data.getBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK,
|
||||||
|
mMetadata.bitmap);
|
||||||
populateMetadata();
|
populateMetadata();
|
||||||
} else {
|
} else {
|
||||||
mPopulateMetadataWhenAttached = data;
|
mPopulateMetadataWhenAttached = data;
|
||||||
@@ -270,12 +362,22 @@ public class KeyguardTransportControlView extends FrameLayout implements OnClick
|
|||||||
* Populates the given metadata into the view
|
* Populates the given metadata into the view
|
||||||
*/
|
*/
|
||||||
private void populateMetadata() {
|
private void populateMetadata() {
|
||||||
StringBuilder sb = new StringBuilder();
|
if (ANIMATE_TRANSITIONS && isLaidOut() && mMetadataContainer.getVisibility() == VISIBLE) {
|
||||||
int trackTitleLength = 0;
|
TransitionManager.beginDelayedTransition(mMetadataContainer, mMetadataChangeTransition);
|
||||||
if (!TextUtils.isEmpty(mMetadata.trackTitle)) {
|
|
||||||
sb.append(mMetadata.trackTitle);
|
|
||||||
trackTitleLength = mMetadata.trackTitle.length();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String remoteClientPackage = mRemoteController.getRemoteControlClientPackageName();
|
||||||
|
Drawable badgeIcon = null;
|
||||||
|
try {
|
||||||
|
badgeIcon = getContext().getPackageManager().getApplicationIcon(remoteClientPackage);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
Log.e(TAG, "Couldn't get remote control client package icon", e);
|
||||||
|
}
|
||||||
|
setBadgeIcon(badgeIcon);
|
||||||
|
if (!TextUtils.isEmpty(mMetadata.trackTitle)) {
|
||||||
|
mTrackTitle.setText(mMetadata.trackTitle);
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
if (!TextUtils.isEmpty(mMetadata.artist)) {
|
if (!TextUtils.isEmpty(mMetadata.artist)) {
|
||||||
if (sb.length() != 0) {
|
if (sb.length() != 0) {
|
||||||
sb.append(" - ");
|
sb.append(" - ");
|
||||||
@@ -288,16 +390,27 @@ public class KeyguardTransportControlView extends FrameLayout implements OnClick
|
|||||||
}
|
}
|
||||||
sb.append(mMetadata.albumTitle);
|
sb.append(mMetadata.albumTitle);
|
||||||
}
|
}
|
||||||
mTrackTitle.setText(sb.toString(), TextView.BufferType.SPANNABLE);
|
mTrackArtistAlbum.setText(sb.toString());
|
||||||
Spannable str = (Spannable) mTrackTitle.getText();
|
|
||||||
if (trackTitleLength != 0) {
|
if (mMetadata.duration >= 0) {
|
||||||
str.setSpan(new ForegroundColorSpan(0xffffffff), 0, trackTitleLength,
|
setSeekBarsEnabled(true);
|
||||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
setSeekBarDuration(mMetadata.duration);
|
||||||
trackTitleLength++;
|
|
||||||
}
|
final String skeleton;
|
||||||
if (sb.length() > trackTitleLength) {
|
|
||||||
str.setSpan(new ForegroundColorSpan(0x7fffffff), trackTitleLength, sb.length(),
|
if (mMetadata.duration >= 86400000) {
|
||||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
skeleton = "DDD kk mm ss";
|
||||||
|
} else if (mMetadata.duration >= 3600000) {
|
||||||
|
skeleton = "kk mm ss";
|
||||||
|
} else {
|
||||||
|
skeleton = "mm ss";
|
||||||
|
}
|
||||||
|
mFormat = new SimpleDateFormat(DateFormat.getBestDateTimePattern(
|
||||||
|
getContext().getResources().getConfiguration().locale,
|
||||||
|
skeleton));
|
||||||
|
mFormat.setTimeZone(TimeZone.getTimeZone("GMT+0"));
|
||||||
|
} else {
|
||||||
|
setSeekBarsEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(
|
KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(
|
||||||
@@ -314,6 +427,66 @@ public class KeyguardTransportControlView extends FrameLayout implements OnClick
|
|||||||
updatePlayPauseState(mCurrentPlayState);
|
updatePlayPauseState(mCurrentPlayState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateSeekDisplay() {
|
||||||
|
if (mMetadata != null && mRemoteController != null && mFormat != null) {
|
||||||
|
final long timeElapsed = mRemoteController.getEstimatedMediaPosition();
|
||||||
|
final long duration = mMetadata.duration;
|
||||||
|
final long remaining = duration - timeElapsed;
|
||||||
|
|
||||||
|
mTransientSeekTimeElapsed.setText(mFormat.format(new Date(timeElapsed)));
|
||||||
|
mTransientSeekTimeRemaining.setText(mFormat.format(new Date(remaining)));
|
||||||
|
|
||||||
|
if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + timeElapsed +
|
||||||
|
" duration=" + duration + " remaining=" + remaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean tryToggleSeekBar() {
|
||||||
|
if (ANIMATE_TRANSITIONS) {
|
||||||
|
TransitionManager.beginDelayedTransition(mInfoContainer);
|
||||||
|
}
|
||||||
|
if (mTransientSeek.getVisibility() == VISIBLE) {
|
||||||
|
mTransientSeek.setVisibility(INVISIBLE);
|
||||||
|
mMetadataContainer.setVisibility(VISIBLE);
|
||||||
|
cancelResetToMetadata();
|
||||||
|
} else {
|
||||||
|
mTransientSeek.setVisibility(VISIBLE);
|
||||||
|
mMetadataContainer.setVisibility(INVISIBLE);
|
||||||
|
delayResetToMetadata();
|
||||||
|
}
|
||||||
|
mTransportControlCallback.userActivity();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetToMetadata() {
|
||||||
|
if (ANIMATE_TRANSITIONS) {
|
||||||
|
TransitionManager.beginDelayedTransition(mInfoContainer);
|
||||||
|
}
|
||||||
|
if (mTransientSeek.getVisibility() == VISIBLE) {
|
||||||
|
mTransientSeek.setVisibility(INVISIBLE);
|
||||||
|
mMetadataContainer.setVisibility(VISIBLE);
|
||||||
|
}
|
||||||
|
// TODO Also hide ratings, if applicable
|
||||||
|
}
|
||||||
|
|
||||||
|
void delayResetToMetadata() {
|
||||||
|
removeCallbacks(mResetToMetadata);
|
||||||
|
postDelayed(mResetToMetadata, RESET_TO_METADATA_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancelResetToMetadata() {
|
||||||
|
removeCallbacks(mResetToMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSeekBarDuration(long duration) {
|
||||||
|
mTransientSeekBar.setMax((int) duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scrubTo(int progress) {
|
||||||
|
mRemoteController.seekTo(progress);
|
||||||
|
mTransportControlCallback.userActivity();
|
||||||
|
}
|
||||||
|
|
||||||
private static void setVisibilityBasedOnFlag(View view, int flags, int flag) {
|
private static void setVisibilityBasedOnFlag(View view, int flags, int flag) {
|
||||||
if ((flags & flag) != 0) {
|
if ((flags & flag) != 0) {
|
||||||
view.setVisibility(View.VISIBLE);
|
view.setVisibility(View.VISIBLE);
|
||||||
@@ -341,6 +514,9 @@ public class KeyguardTransportControlView extends FrameLayout implements OnClick
|
|||||||
case RemoteControlClient.PLAYSTATE_PLAYING:
|
case RemoteControlClient.PLAYSTATE_PLAYING:
|
||||||
imageResId = R.drawable.ic_media_pause;
|
imageResId = R.drawable.ic_media_pause;
|
||||||
imageDescId = R.string.keyguard_transport_pause_description;
|
imageDescId = R.string.keyguard_transport_pause_description;
|
||||||
|
if (mSeekEnabled) {
|
||||||
|
postDelayed(mUpdateSeekBars, 1000);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RemoteControlClient.PLAYSTATE_BUFFERING:
|
case RemoteControlClient.PLAYSTATE_BUFFERING:
|
||||||
@@ -354,11 +530,30 @@ public class KeyguardTransportControlView extends FrameLayout implements OnClick
|
|||||||
imageDescId = R.string.keyguard_transport_play_description;
|
imageDescId = R.string.keyguard_transport_play_description;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state != RemoteControlClient.PLAYSTATE_PLAYING) {
|
||||||
|
removeCallbacks(mUpdateSeekBars);
|
||||||
|
updateSeekBars();
|
||||||
|
}
|
||||||
mBtnPlay.setImageResource(imageResId);
|
mBtnPlay.setImageResource(imageResId);
|
||||||
mBtnPlay.setContentDescription(getResources().getString(imageDescId));
|
mBtnPlay.setContentDescription(getResources().getString(imageDescId));
|
||||||
mCurrentPlayState = state;
|
mCurrentPlayState = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean updateSeekBars() {
|
||||||
|
final int position = (int) mRemoteController.getEstimatedMediaPosition();
|
||||||
|
if (position >= 0) {
|
||||||
|
if (!mUserSeeking) {
|
||||||
|
mTransientSeekBar.setProgress(position);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Log.w(TAG, "Updating seek bars; received invalid estimated media position (" +
|
||||||
|
position + "). Disabling seek.");
|
||||||
|
setSeekBarsEnabled(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static class SavedState extends BaseSavedState {
|
static class SavedState extends BaseSavedState {
|
||||||
boolean clientPresent;
|
boolean clientPresent;
|
||||||
|
|
||||||
@@ -389,48 +584,13 @@ public class KeyguardTransportControlView extends FrameLayout implements OnClick
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onClick(View v) {
|
|
||||||
int keyCode = -1;
|
|
||||||
if (v == mBtnPrev) {
|
|
||||||
keyCode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
|
|
||||||
} else if (v == mBtnNext) {
|
|
||||||
keyCode = KeyEvent.KEYCODE_MEDIA_NEXT;
|
|
||||||
} else if (v == mBtnPlay) {
|
|
||||||
keyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
|
|
||||||
|
|
||||||
}
|
|
||||||
if (keyCode != -1) {
|
|
||||||
sendMediaButtonClick(keyCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMediaButtonClick(int keyCode) {
|
private void sendMediaButtonClick(int keyCode) {
|
||||||
if (mClientIntent == null) {
|
// TODO We should think about sending these up/down events accurately with touch up/down
|
||||||
// Shouldn't be possible because this view should be hidden in this case.
|
// on the buttons, but in the near term this will interfere with the long press behavior.
|
||||||
Log.e(TAG, "sendMediaButtonClick(): No client is currently registered");
|
mRemoteController.sendMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
||||||
return;
|
mRemoteController.sendMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
||||||
}
|
|
||||||
// use the registered PendingIntent that will be processed by the registered
|
|
||||||
// media button event receiver, which is the component of mClientIntent
|
|
||||||
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
|
|
||||||
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
|
||||||
intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
|
|
||||||
try {
|
|
||||||
mClientIntent.send(getContext(), 0, intent);
|
|
||||||
} catch (CanceledException e) {
|
|
||||||
Log.e(TAG, "Error sending intent for media button down: "+e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
|
mTransportControlCallback.userActivity();
|
||||||
intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
|
||||||
intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
|
|
||||||
try {
|
|
||||||
mClientIntent.send(getContext(), 0, intent);
|
|
||||||
} catch (CanceledException e) {
|
|
||||||
Log.e(TAG, "Error sending intent for media button up: "+e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean providesClock() {
|
public boolean providesClock() {
|
||||||
|
|||||||
Reference in New Issue
Block a user