Merge "Initial pass at adding Music control to new keyguard." into jb-mr1-dev
This commit is contained in:
@@ -35,6 +35,8 @@
|
||||
|
||||
<!-- TODO: Remove this once supported as a widget -->
|
||||
<include layout="@layout/keyguard_status_view"/>
|
||||
<include layout="@layout/keyguard_transport_control_view"/>
|
||||
|
||||
</com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
|
||||
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
<!-- TODO: Remove this once supported as a widget -->
|
||||
<include layout="@layout/keyguard_status_view"/>
|
||||
<include layout="@layout/keyguard_transport_control_view"/>
|
||||
|
||||
</com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
|
||||
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal"
|
||||
android:clipChildren="false">
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<com.android.internal.policy.impl.keyguard.KeyguardWidgetPager
|
||||
android:id="@+id/app_widget_container"
|
||||
@@ -37,6 +36,7 @@
|
||||
|
||||
<!-- TODO: Remove this once supported as a widget -->
|
||||
<include layout="@layout/keyguard_status_view"/>
|
||||
<include layout="@layout/keyguard_transport_control_view"/>
|
||||
|
||||
</com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
android:visibility="gone">
|
||||
<!-- TODO: Remove this when supported as a widget -->
|
||||
<include layout="@layout/keyguard_status_view"/>
|
||||
<include layout="@layout/keyguard_transport_control_view"/>
|
||||
</com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
|
||||
|
||||
<RelativeLayout
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/keyguard_status_view"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<com.android.internal.policy.impl.keyguard.KeyguardStatusView
|
||||
|
||||
@@ -14,19 +14,20 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Note: This file is meant to be included in various password unlock screens. As such,
|
||||
LayoutParams (layout_*) for TransportControlView should *NOT* be specified here,
|
||||
but rather as include tags for this file or the layout will break. -->
|
||||
<com.android.internal.widget.TransportControlView
|
||||
<!-- This is a view to control music playback in keyguard. -->
|
||||
<com.android.internal.policy.impl.keyguard.KeyguardTransportControlView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/transport_controls">
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:id="@+id/keyguard_transport_control">
|
||||
|
||||
<!-- FrameLayout used as scrim to show between album art and buttons -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:foreground="@drawable/ic_lockscreen_player_background">
|
||||
<!-- We use ImageView for its cropping features, otherwise could be android:background -->
|
||||
<!-- Use ImageView for its cropping features; otherwise could be android:background -->
|
||||
<ImageView
|
||||
android:id="@+id/albumart"
|
||||
android:layout_width="match_parent"
|
||||
@@ -107,4 +108,4 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</com.android.internal.widget.TransportControlView>
|
||||
</com.android.internal.policy.impl.keyguard.KeyguardTransportControlView>
|
||||
@@ -1343,6 +1343,8 @@
|
||||
<java-symbol type="id" name="keyguard_user_name" />
|
||||
<java-symbol type="id" name="keyguard_active_user" />
|
||||
<java-symbol type="id" name="keyguard_inactive_users" />
|
||||
<java-symbol type="id" name="keyguard_transport_control" />
|
||||
<java-symbol type="id" name="keyguard_status_view" />
|
||||
<java-symbol type="integer" name="config_carDockRotation" />
|
||||
<java-symbol type="integer" name="config_defaultUiModeType" />
|
||||
<java-symbol type="integer" name="config_deskDockRotation" />
|
||||
|
||||
@@ -41,6 +41,8 @@ import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.RemoteViews.OnClickHandler;
|
||||
@@ -54,13 +56,14 @@ import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public class KeyguardHostView extends KeyguardViewBase {
|
||||
private static final String TAG = "KeyguardViewHost";
|
||||
|
||||
// Use this to debug all of keyguard
|
||||
public static boolean DEBUG;
|
||||
|
||||
static final int APPWIDGET_HOST_ID = 0x4B455947;
|
||||
private static final String KEYGUARD_WIDGET_PREFS = "keyguard_widget_prefs";
|
||||
|
||||
private static final String TAG = "KeyguardViewHost";
|
||||
private AppWidgetHost mAppWidgetHost;
|
||||
private KeyguardWidgetPager mAppWidgetContainer;
|
||||
private ViewFlipper mSecurityViewContainer;
|
||||
@@ -77,6 +80,12 @@ public class KeyguardHostView extends KeyguardViewBase {
|
||||
private KeyguardSecurityModel mSecurityModel;
|
||||
|
||||
private Rect mTempRect = new Rect();
|
||||
private KeyguardTransportControlView mTransportControl;
|
||||
|
||||
/*package*/ interface TransportCallback {
|
||||
void hide();
|
||||
void show();
|
||||
}
|
||||
|
||||
public KeyguardHostView(Context context) {
|
||||
this(context, null);
|
||||
@@ -111,11 +120,56 @@ public class KeyguardHostView extends KeyguardViewBase {
|
||||
mViewMediatorCallback.keyguardDoneDrawing();
|
||||
}
|
||||
|
||||
private int getWidgetPosition(int id) {
|
||||
final int children = mAppWidgetContainer.getChildCount();
|
||||
for (int i = 0; i < children; i++) {
|
||||
if (mAppWidgetContainer.getChildAt(i).getId() == id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
mAppWidgetContainer = (KeyguardWidgetPager) findViewById(R.id.app_widget_container);
|
||||
mAppWidgetContainer.setVisibility(VISIBLE);
|
||||
mSecurityViewContainer = (ViewFlipper) findViewById(R.id.view_flipper);
|
||||
|
||||
// This code manages showing/hiding the transport control. We keep it around and only
|
||||
// add it to the hierarchy if it needs to be present.
|
||||
mTransportControl =
|
||||
(KeyguardTransportControlView) findViewById(R.id.keyguard_transport_control);
|
||||
if (mTransportControl != null) {
|
||||
mTransportControl.setKeyguardCallback(new TransportCallback() {
|
||||
@Override
|
||||
public void hide() {
|
||||
int page = getWidgetPosition(R.id.keyguard_transport_control);
|
||||
if (page != -1) {
|
||||
if (page == mAppWidgetContainer.getCurrentPage()) {
|
||||
// Switch back to clock view if music was showing.
|
||||
mAppWidgetContainer
|
||||
.setCurrentPage(getWidgetPosition(R.id.keyguard_status_view));
|
||||
}
|
||||
mAppWidgetContainer.removeView(mTransportControl);
|
||||
// XXX keep view attached to hierarchy so we still get show/hide events
|
||||
// from AudioManager
|
||||
KeyguardHostView.this.addView(mTransportControl);
|
||||
mTransportControl.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
if (getWidgetPosition(R.id.keyguard_transport_control) == -1) {
|
||||
KeyguardHostView.this.removeView(mTransportControl);
|
||||
mAppWidgetContainer.addView(mTransportControl,
|
||||
getWidgetPosition(R.id.keyguard_status_view) + 1);
|
||||
mTransportControl.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
updateSecurityViews();
|
||||
}
|
||||
|
||||
@@ -423,6 +477,7 @@ public class KeyguardHostView extends KeyguardViewBase {
|
||||
@Override
|
||||
public void reset() {
|
||||
mIsVerifyUnlockOnly = false;
|
||||
mAppWidgetContainer.setCurrentPage(getWidgetPosition(R.id.keyguard_status_view));
|
||||
requestFocus();
|
||||
}
|
||||
|
||||
@@ -639,8 +694,8 @@ public class KeyguardHostView extends KeyguardViewBase {
|
||||
KeyguardWidgetFrame userswitcher = (KeyguardWidgetFrame)
|
||||
LayoutInflater.from(mContext).inflate(R.layout.keyguard_multi_user_selector_widget,
|
||||
mAppWidgetContainer, false);
|
||||
// add the switcher in the first position
|
||||
mAppWidgetContainer.addView(userswitcher, 0);
|
||||
// add the switcher to the left of status view
|
||||
mAppWidgetContainer.addView(userswitcher, getWidgetPosition(R.id.keyguard_status_view));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,534 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.policy.impl.keyguard;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.PendingIntent.CanceledException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.media.RemoteControlClient;
|
||||
import android.media.IRemoteControlDisplay;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.text.Spannable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.policy.impl.keyguard.KeyguardHostView.TransportCallback;
|
||||
|
||||
/**
|
||||
* This is the widget responsible for showing music controls in keyguard.
|
||||
*/
|
||||
public class KeyguardTransportControlView extends KeyguardWidgetFrame implements OnClickListener {
|
||||
|
||||
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 MAXDIM = 512;
|
||||
private static final int DISPLAY_TIMEOUT_MS = 5000; // 5s
|
||||
protected static final boolean DEBUG = false;
|
||||
protected static final String TAG = "TransportControlView";
|
||||
|
||||
private ImageView mAlbumArt;
|
||||
private TextView mTrackTitle;
|
||||
private ImageView mBtnPrev;
|
||||
private ImageView mBtnPlay;
|
||||
private ImageView mBtnNext;
|
||||
private int mClientGeneration;
|
||||
private Metadata mMetadata = new Metadata();
|
||||
private boolean mAttached;
|
||||
private PendingIntent mClientIntent;
|
||||
private int mTransportControlFlags;
|
||||
private int mCurrentPlayState;
|
||||
private AudioManager mAudioManager;
|
||||
private IRemoteControlDisplayWeak mIRCD;
|
||||
|
||||
/**
|
||||
* The metadata which should be populated into the view once we've been attached
|
||||
*/
|
||||
private Bundle mPopulateMetadataWhenAttached = null;
|
||||
|
||||
// This handler is required to ensure messages from IRCD are handled in sequence and on
|
||||
// the UI thread.
|
||||
private Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_UPDATE_STATE:
|
||||
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) {
|
||||
if (mMetadata.bitmap != null) {
|
||||
mMetadata.bitmap.recycle();
|
||||
}
|
||||
mMetadata.bitmap = (Bitmap) msg.obj;
|
||||
mAlbumArt.setImageBitmap(mMetadata.bitmap);
|
||||
}
|
||||
break;
|
||||
|
||||
case MSG_SET_GENERATION_ID:
|
||||
if (msg.arg2 != 0) {
|
||||
// This means nobody is currently registered. Hide the view.
|
||||
hide();
|
||||
}
|
||||
if (DEBUG) Log.v(TAG, "New genId = " + msg.arg1 + ", clearing = " + msg.arg2);
|
||||
mClientGeneration = msg.arg1;
|
||||
mClientIntent = (PendingIntent) msg.obj;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
private TransportCallback mTransportCallback;
|
||||
|
||||
/**
|
||||
* This class is required to have weak linkage to the current TransportControlView
|
||||
* because the remote process can hold a strong reference to this binder object and
|
||||
* we can't predict when it will be GC'd in the remote process. Without this code, it
|
||||
* 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) {
|
||||
Handler handler = mLocalHandler.get();
|
||||
if (handler != null) {
|
||||
handler.obtainMessage(MSG_UPDATE_STATE, generationId, state).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMetadata(int generationId, Bundle metadata) {
|
||||
Handler handler = mLocalHandler.get();
|
||||
if (handler != null) {
|
||||
handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTransportControlFlags(int generationId, int flags) {
|
||||
Handler handler = mLocalHandler.get();
|
||||
if (handler != null) {
|
||||
handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags)
|
||||
.sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
public void setArtwork(int generationId, Bitmap bitmap) {
|
||||
Handler handler = mLocalHandler.get();
|
||||
if (handler != null) {
|
||||
handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public KeyguardTransportControlView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
Log.v(TAG, "Create TCV " + this);
|
||||
mAudioManager = new AudioManager(mContext);
|
||||
mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
|
||||
mIRCD = new IRemoteControlDisplayWeak(mHandler);
|
||||
}
|
||||
|
||||
protected void hide() {
|
||||
if (DEBUG) Log.v(TAG, "Transport was told to hide");
|
||||
if (mTransportCallback != null) {
|
||||
mTransportCallback.hide();
|
||||
} else {
|
||||
Log.w(TAG, "Hide music, but callback wasn't set");
|
||||
}
|
||||
}
|
||||
|
||||
private void show() {
|
||||
if (DEBUG) Log.v(TAG, "Transport was told to show");
|
||||
if (mTransportCallback != null) {
|
||||
mTransportCallback.show();
|
||||
} else {
|
||||
Log.w(TAG, "Show music, but callback wasn't set");
|
||||
}
|
||||
}
|
||||
|
||||
private void userActivity() {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
private void updateTransportControls(int transportControlFlags) {
|
||||
mTransportControlFlags = transportControlFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mTrackTitle = (TextView) findViewById(R.id.title);
|
||||
mTrackTitle.setSelected(true); // enable marquee
|
||||
mAlbumArt = (ImageView) findViewById(R.id.albumart);
|
||||
mBtnPrev = (ImageView) findViewById(R.id.btn_prev);
|
||||
mBtnPlay = (ImageView) findViewById(R.id.btn_play);
|
||||
mBtnNext = (ImageView) findViewById(R.id.btn_next);
|
||||
final View buttons[] = { mBtnPrev, mBtnPlay, mBtnNext };
|
||||
for (View view : buttons) {
|
||||
view.setOnClickListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
if (DEBUG) Log.v(TAG, "onAttachToWindow()");
|
||||
if (mPopulateMetadataWhenAttached != null) {
|
||||
updateMetadata(mPopulateMetadataWhenAttached);
|
||||
mPopulateMetadataWhenAttached = null;
|
||||
}
|
||||
if (!mAttached) {
|
||||
if (DEBUG) Log.v(TAG, "Registering TCV " + this);
|
||||
mAudioManager.registerRemoteControlDisplay(mIRCD);
|
||||
}
|
||||
mAttached = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
if (DEBUG) Log.v(TAG, "onDetachFromWindow()");
|
||||
super.onDetachedFromWindow();
|
||||
if (mAttached) {
|
||||
if (DEBUG) Log.v(TAG, "Unregistering TCV " + this);
|
||||
mAudioManager.unregisterRemoteControlDisplay(mIRCD);
|
||||
}
|
||||
mAttached = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int dim = Math.min(MAXDIM, Math.max(getWidth(), getHeight()));
|
||||
// Log.v(TAG, "setting max bitmap size: " + dim + "x" + dim);
|
||||
// mAudioManager.remoteControlDisplayUsesBitmapSize(mIRCD, dim, dim);
|
||||
}
|
||||
|
||||
class Metadata {
|
||||
private String artist;
|
||||
private String trackTitle;
|
||||
private String albumTitle;
|
||||
private Bitmap bitmap;
|
||||
|
||||
public String toString() {
|
||||
return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + " albumTitle=" + albumTitle + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private String getMdString(Bundle data, int id) {
|
||||
return data.getString(Integer.toString(id));
|
||||
}
|
||||
|
||||
private void updateMetadata(Bundle data) {
|
||||
if (mAttached) {
|
||||
mMetadata.artist = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
|
||||
mMetadata.trackTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_TITLE);
|
||||
mMetadata.albumTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUM);
|
||||
populateMetadata();
|
||||
} else {
|
||||
mPopulateMetadataWhenAttached = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the given metadata into the view
|
||||
*/
|
||||
private void populateMetadata() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int trackTitleLength = 0;
|
||||
if (!TextUtils.isEmpty(mMetadata.trackTitle)) {
|
||||
sb.append(mMetadata.trackTitle);
|
||||
trackTitleLength = mMetadata.trackTitle.length();
|
||||
}
|
||||
if (!TextUtils.isEmpty(mMetadata.artist)) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append(" - ");
|
||||
}
|
||||
sb.append(mMetadata.artist);
|
||||
}
|
||||
if (!TextUtils.isEmpty(mMetadata.albumTitle)) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append(" - ");
|
||||
}
|
||||
sb.append(mMetadata.albumTitle);
|
||||
}
|
||||
mTrackTitle.setText(sb.toString(), TextView.BufferType.SPANNABLE);
|
||||
Spannable str = (Spannable) mTrackTitle.getText();
|
||||
if (trackTitleLength != 0) {
|
||||
str.setSpan(new ForegroundColorSpan(0xffffffff), 0, trackTitleLength,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
trackTitleLength++;
|
||||
}
|
||||
if (sb.length() > trackTitleLength) {
|
||||
str.setSpan(new ForegroundColorSpan(0x7fffffff), trackTitleLength, sb.length(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
mAlbumArt.setImageBitmap(mMetadata.bitmap);
|
||||
final int flags = mTransportControlFlags;
|
||||
setVisibilityBasedOnFlag(mBtnPrev, flags, RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS);
|
||||
setVisibilityBasedOnFlag(mBtnNext, flags, RemoteControlClient.FLAG_KEY_MEDIA_NEXT);
|
||||
setVisibilityBasedOnFlag(mBtnPlay, flags,
|
||||
RemoteControlClient.FLAG_KEY_MEDIA_PLAY
|
||||
| RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
|
||||
| RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
|
||||
| RemoteControlClient.FLAG_KEY_MEDIA_STOP);
|
||||
|
||||
updatePlayPauseState(mCurrentPlayState);
|
||||
}
|
||||
|
||||
private static void setVisibilityBasedOnFlag(View view, int flags, int flag) {
|
||||
if ((flags & flag) != 0) {
|
||||
view.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
view.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlayPauseState(int state) {
|
||||
if (DEBUG) Log.v(TAG,
|
||||
"updatePlayPauseState(), old=" + mCurrentPlayState + ", state=" + state);
|
||||
if (state == mCurrentPlayState) {
|
||||
return;
|
||||
}
|
||||
final int imageResId;
|
||||
final int imageDescId;
|
||||
boolean showIfHidden = false;
|
||||
switch (state) {
|
||||
case RemoteControlClient.PLAYSTATE_ERROR:
|
||||
imageResId = com.android.internal.R.drawable.stat_sys_warning;
|
||||
// TODO use more specific image description string for warning, but here the "play"
|
||||
// message is still valid because this button triggers a play command.
|
||||
imageDescId = com.android.internal.R.string.lockscreen_transport_play_description;
|
||||
break;
|
||||
|
||||
case RemoteControlClient.PLAYSTATE_PLAYING:
|
||||
imageResId = com.android.internal.R.drawable.ic_media_pause;
|
||||
imageDescId = com.android.internal.R.string.lockscreen_transport_pause_description;
|
||||
showIfHidden = true;
|
||||
break;
|
||||
|
||||
case RemoteControlClient.PLAYSTATE_BUFFERING:
|
||||
imageResId = com.android.internal.R.drawable.ic_media_stop;
|
||||
imageDescId = com.android.internal.R.string.lockscreen_transport_stop_description;
|
||||
showIfHidden = true;
|
||||
break;
|
||||
|
||||
case RemoteControlClient.PLAYSTATE_PAUSED:
|
||||
default:
|
||||
imageResId = com.android.internal.R.drawable.ic_media_play;
|
||||
imageDescId = com.android.internal.R.string.lockscreen_transport_play_description;
|
||||
showIfHidden = false;
|
||||
break;
|
||||
}
|
||||
mBtnPlay.setImageResource(imageResId);
|
||||
mBtnPlay.setContentDescription(getResources().getString(imageDescId));
|
||||
if (showIfHidden) {
|
||||
show();
|
||||
}
|
||||
mCurrentPlayState = state;
|
||||
}
|
||||
|
||||
static class SavedState extends BaseSavedState {
|
||||
boolean wasShowing;
|
||||
|
||||
SavedState(Parcelable superState) {
|
||||
super(superState);
|
||||
}
|
||||
|
||||
private SavedState(Parcel in) {
|
||||
super(in);
|
||||
this.wasShowing = in.readInt() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
super.writeToParcel(out, flags);
|
||||
out.writeInt(this.wasShowing ? 1 : 0);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SavedState> CREATOR
|
||||
= new Parcelable.Creator<SavedState>() {
|
||||
public SavedState createFromParcel(Parcel in) {
|
||||
return new SavedState(in);
|
||||
}
|
||||
|
||||
public SavedState[] newArray(int size) {
|
||||
return new SavedState[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
if (DEBUG) Log.v(TAG, "onSaveInstanceState()");
|
||||
Parcelable superState = super.onSaveInstanceState();
|
||||
SavedState ss = new SavedState(superState);
|
||||
ss.wasShowing = getVisibility() == View.VISIBLE;
|
||||
return ss;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
if (DEBUG) Log.v(TAG, "onRestoreInstanceState()");
|
||||
if (!(state instanceof SavedState)) {
|
||||
super.onRestoreInstanceState(state);
|
||||
return;
|
||||
}
|
||||
SavedState ss = (SavedState) state;
|
||||
super.onRestoreInstanceState(ss.getSuperState());
|
||||
if (ss.wasShowing) {
|
||||
show();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
userActivity();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMediaButtonClick(int keyCode) {
|
||||
if (mClientIntent == null) {
|
||||
// Shouldn't be possible because this view should be hidden in this case.
|
||||
Log.e(TAG, "sendMediaButtonClick(): No client is currently registered");
|
||||
return;
|
||||
}
|
||||
// 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);
|
||||
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() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean wasPlayingRecently(int state, long stateChangeTimeMs) {
|
||||
switch (state) {
|
||||
case RemoteControlClient.PLAYSTATE_PLAYING:
|
||||
case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
|
||||
case RemoteControlClient.PLAYSTATE_REWINDING:
|
||||
case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
|
||||
case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
|
||||
case RemoteControlClient.PLAYSTATE_BUFFERING:
|
||||
// actively playing or about to play
|
||||
return true;
|
||||
case RemoteControlClient.PLAYSTATE_NONE:
|
||||
return false;
|
||||
case RemoteControlClient.PLAYSTATE_STOPPED:
|
||||
case RemoteControlClient.PLAYSTATE_PAUSED:
|
||||
case RemoteControlClient.PLAYSTATE_ERROR:
|
||||
// we have stopped playing, check how long ago
|
||||
if (DEBUG) {
|
||||
if ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS) {
|
||||
Log.v(TAG, "wasPlayingRecently: time < TIMEOUT was playing recently");
|
||||
} else {
|
||||
Log.v(TAG, "wasPlayingRecently: time > TIMEOUT");
|
||||
}
|
||||
}
|
||||
return ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS);
|
||||
default:
|
||||
Log.e(TAG, "Unknown playback state " + state + " in wasPlayingRecently()");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setKeyguardCallback(TransportCallback transportCallback) {
|
||||
mTransportCallback = transportCallback;
|
||||
}
|
||||
}
|
||||
@@ -595,6 +595,10 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
|
||||
|
||||
@Override
|
||||
public void onChildViewRemoved(View parent, View child) {
|
||||
invalidate();
|
||||
invalidateCachedOffsets();
|
||||
// This prevents a crash when a child is removed that was the current page.
|
||||
mCurrentPage = Math.min(mCurrentPage, getChildCount() - 1);
|
||||
}
|
||||
|
||||
protected void invalidateCachedOffsets() {
|
||||
|
||||
Reference in New Issue
Block a user