是否可以在锁定屏幕上创建Google Play音乐等小部件?

时间:2014-03-18 09:30:42

标签: android service widget lockscreen

我正在开发一个音乐播放器。我有一个可在家庭和锁定屏幕上工作的小部件。我不想让用户选择在锁定屏幕上放置或删除小部件。

我的目标是:

  1. 窗口小部件会自动显示在主机锁定屏幕上,删除作为时间日期窗口小部件或放置它的窗口小部件。

  2. 始终如一地管理MusicService和Widget。 (当我离开音乐播放器时,从锁定屏幕清除小部件。 这正是我的Galaxy Nexus(或许多其他手机)上的Google Play音乐小部件。这可能吗?是否有可能我需要一些不是小工具的东西,比如自定义锁屏?

1 个答案:

答案 0 :(得分:4)

嗯,解决方案很简单,不要使用任何小部件,只需使用RemoteControlClientCompat类。这是我的lockScreenControls()方法代码,每当我想显示这种类型的控件时,我都会调用它。

private void lockScreenControls() {

        // Use the media button APIs (if available) to register ourselves for media button
        // events

        MediaButtonHelper.registerMediaButtonEventReceiverCompat(mAudioManager, mMediaButtonReceiverComponent);
        // Use the remote control APIs (if available) to set the playback state
        if (mRemoteControlClientCompat == null) {
            Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
            intent.setComponent(mMediaButtonReceiverComponent);
            mRemoteControlClientCompat = new RemoteControlClientCompat(PendingIntent.getBroadcast(this /*context*/,0 /*requestCode, ignored*/, intent /*intent*/, 0 /*flags*/));
            RemoteControlHelper.registerRemoteControlClient(mAudioManager,mRemoteControlClientCompat);
        }
        mRemoteControlClientCompat.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
        mRemoteControlClientCompat.setTransportControlFlags(
                RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
                RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS |
                RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
                RemoteControlClient.FLAG_KEY_MEDIA_STOP);

        //update remote controls
        mRemoteControlClientCompat.editMetadata(true)
                .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, "NombreArtista")
                .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, "Titulo Album")
                .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, nombreCancion)
                //.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION,playingItem.getDuration())
                        // TODO: fetch real item artwork
                .putBitmap(RemoteControlClientCompat.MetadataEditorCompat.METADATA_KEY_ARTWORK, getAlbumArt())
                .apply();
        }
    }

<强> *********** EDIT ************

RemoteControlClientCompat类:

@SuppressWarnings({"rawtypes", "unchecked"})
public class RemoteControlClientCompat {

private static final String TAG = "RemoteControlCompat";

private static Class sRemoteControlClientClass;

// RCC short for RemoteControlClient
private static Method sRCCEditMetadataMethod;
private static Method sRCCSetPlayStateMethod;
private static Method sRCCSetTransportControlFlags;

private static boolean sHasRemoteControlAPIs = false;

static {
    try {
        ClassLoader classLoader = RemoteControlClientCompat.class.getClassLoader();
        sRemoteControlClientClass = getActualRemoteControlClientClass(classLoader);
        // dynamically populate the playstate and flag values in case they change
        // in future versions.
        for (Field field : RemoteControlClientCompat.class.getFields()) {
            try {
                Field realField = sRemoteControlClientClass.getField(field.getName());
                Object realValue = realField.get(null);
                field.set(null, realValue);
            } catch (NoSuchFieldException e) {
                Log.w(TAG, "Could not get real field: " + field.getName());
            } catch (IllegalArgumentException e) {
                Log.w(TAG, "Error trying to pull field value for: " + field.getName()
                        + " " + e.getMessage());
            } catch (IllegalAccessException e) {
                Log.w(TAG, "Error trying to pull field value for: " + field.getName()
                        + " " + e.getMessage());
            }
        }

        // get the required public methods on RemoteControlClient
        sRCCEditMetadataMethod = sRemoteControlClientClass.getMethod("editMetadata",
                boolean.class);
        sRCCSetPlayStateMethod = sRemoteControlClientClass.getMethod("setPlaybackState",
                int.class);
        sRCCSetTransportControlFlags = sRemoteControlClientClass.getMethod(
                "setTransportControlFlags", int.class);

        sHasRemoteControlAPIs = true;
    } catch (ClassNotFoundException e) {
        // Silently fail when running on an OS before ICS.
    } catch (NoSuchMethodException e) {
        // Silently fail when running on an OS before ICS.
    } catch (IllegalArgumentException e) {
        // Silently fail when running on an OS before ICS.
    } catch (SecurityException e) {
        // Silently fail when running on an OS before ICS.
    }
}

public static Class getActualRemoteControlClientClass(ClassLoader classLoader)
        throws ClassNotFoundException {
    return classLoader.loadClass("android.media.RemoteControlClient");
}

private Object mActualRemoteControlClient;

public RemoteControlClientCompat(PendingIntent pendingIntent) {
    if (!sHasRemoteControlAPIs) {
        return;
    }
    try {
        mActualRemoteControlClient =
                sRemoteControlClientClass.getConstructor(PendingIntent.class)
                        .newInstance(pendingIntent);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

public RemoteControlClientCompat(PendingIntent pendingIntent, Looper looper) {
    if (!sHasRemoteControlAPIs) {
        return;
    }

    try {
        mActualRemoteControlClient =
                sRemoteControlClientClass.getConstructor(PendingIntent.class, Looper.class)
                        .newInstance(pendingIntent, looper);
    } catch (Exception e) {
        Log.e(TAG, "Error creating new instance of " + sRemoteControlClientClass.getName(), e);
    }
}

/**
 * Class used to modify metadata in a {@link android.media.RemoteControlClient} object. Use
 * {@link android.media.RemoteControlClient#editMetadata(boolean)} to create an instance of an
 * editor, on which you set the metadata for the RemoteControlClient instance. Once all the
 * information has been set, use {@link #apply()} to make it the new metadata that should be
 * displayed for the associated client. Once the metadata has been "applied", you cannot reuse
 * this instance of the MetadataEditor.
 */
public class MetadataEditorCompat {

    private Method mPutStringMethod;
    private Method mPutBitmapMethod;
    private Method mPutLongMethod;
    private Method mClearMethod;
    private Method mApplyMethod;

    private Object mActualMetadataEditor;

    /**
     * The metadata key for the content artwork / album art.
     */
    public final static int METADATA_KEY_ARTWORK = 100;

    private MetadataEditorCompat(Object actualMetadataEditor) {
        if (sHasRemoteControlAPIs && actualMetadataEditor == null) {
            throw new IllegalArgumentException("Remote Control API's exist, " +
                    "should not be given a null MetadataEditor");
        }
        if (sHasRemoteControlAPIs) {
            Class metadataEditorClass = actualMetadataEditor.getClass();

            try {
                mPutStringMethod = metadataEditorClass.getMethod("putString",
                        int.class, String.class);
                mPutBitmapMethod = metadataEditorClass.getMethod("putBitmap",
                        int.class, Bitmap.class);
                mPutLongMethod = metadataEditorClass.getMethod("putLong",
                        int.class, long.class);
                mClearMethod = metadataEditorClass.getMethod("clear", new Class[]{});
                mApplyMethod = metadataEditorClass.getMethod("apply", new Class[]{});
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
        mActualMetadataEditor = actualMetadataEditor;
    }

    /**
     * Adds textual information to be displayed.
     * Note that none of the information added after {@link #apply()} has been called,
     * will be displayed.
     * @param key The identifier of a the metadata field to set. Valid values are
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
     * @param value The text for the given key, or {@code null} to signify there is no valid
     *      information for the field.
     * @return Returns a reference to the same MetadataEditor object, so you can chain put
     *      calls together.
     */
    public MetadataEditorCompat putString(int key, String value) {
        if (sHasRemoteControlAPIs) {
            try {
                mPutStringMethod.invoke(mActualMetadataEditor, key, value);
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
        return this;
    }

    /**
     * Sets the album / artwork picture to be displayed on the remote control.
     * @param key the identifier of the bitmap to set. The only valid value is
     *      {@link #METADATA_KEY_ARTWORK}
     * @param bitmap The bitmap for the artwork, or null if there isn't any.
     * @return Returns a reference to the same MetadataEditor object, so you can chain put
     *      calls together.
     * @throws IllegalArgumentException
     * @see android.graphics.Bitmap
     */
    public MetadataEditorCompat putBitmap(int key, Bitmap bitmap) {
        if (sHasRemoteControlAPIs) {
            try {
                mPutBitmapMethod.invoke(mActualMetadataEditor, key, bitmap);
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
        return this;
    }

    /**
     * Adds numerical information to be displayed.
     * Note that none of the information added after {@link #apply()} has been called,
     * will be displayed.
     * @param key the identifier of a the metadata field to set. Valid values are
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
     *      expressed in milliseconds),
     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
     * @param value The long value for the given key
     * @return Returns a reference to the same MetadataEditor object, so you can chain put
     *      calls together.
     * @throws IllegalArgumentException
     */
    public MetadataEditorCompat putLong(int key, long value) {
        if (sHasRemoteControlAPIs) {
            try {
                mPutLongMethod.invoke(mActualMetadataEditor, key, value);
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
        return this;
    }

    /**
     * Clears all the metadata that has been set since the MetadataEditor instance was
     * created with {@link android.media.RemoteControlClient#editMetadata(boolean)}.
     */
    public void clear() {
        if (sHasRemoteControlAPIs) {
            try {
                mClearMethod.invoke(mActualMetadataEditor, (Object[]) null);
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }

    /**
     * Associates all the metadata that has been set since the MetadataEditor instance was
     * created with {@link android.media.RemoteControlClient#editMetadata(boolean)}, or since
     * {@link #clear()} was called, with the RemoteControlClient. Once "applied", this
     * MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
     */
    public void apply() {
        if (sHasRemoteControlAPIs) {
            try {
                mApplyMethod.invoke(mActualMetadataEditor, (Object[]) null);
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }
}

/**
 * Creates a {@link android.media.RemoteControlClient.MetadataEditor}.
 * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that
 *     was previously applied to the RemoteControlClient, or true if it is to be created empty.
 * @return a new MetadataEditor instance.
 */
public MetadataEditorCompat editMetadata(boolean startEmpty) {
    Object metadataEditor;
    if (sHasRemoteControlAPIs) {
        try {
            metadataEditor = sRCCEditMetadataMethod.invoke(mActualRemoteControlClient,
                    startEmpty);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    } else {
        metadataEditor = null;
    }
    return new MetadataEditorCompat(metadataEditor);
}

/**
 * Sets the current playback state.
 * @param state The current playback state, one of the following values:
 *       {@link android.media.RemoteControlClient#PLAYSTATE_STOPPED},
 *       {@link android.media.RemoteControlClient#PLAYSTATE_PAUSED},
 *       {@link android.media.RemoteControlClient#PLAYSTATE_PLAYING},
 *       {@link android.media.RemoteControlClient#PLAYSTATE_FAST_FORWARDING},
 *       {@link android.media.RemoteControlClient#PLAYSTATE_REWINDING},
 *       {@link android.media.RemoteControlClient#PLAYSTATE_SKIPPING_FORWARDS},
 *       {@link android.media.RemoteControlClient#PLAYSTATE_SKIPPING_BACKWARDS},
 *       {@link android.media.RemoteControlClient#PLAYSTATE_BUFFERING},
 *       {@link android.media.RemoteControlClient#PLAYSTATE_ERROR}.
 */
public void setPlaybackState(int state) {
    if (sHasRemoteControlAPIs) {
        try {
            sRCCSetPlayStateMethod.invoke(mActualRemoteControlClient, state);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

/**
 * Sets the flags for the media transport control buttons that this client supports.
 * @param transportControlFlags A combination of the following flags:
 *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PREVIOUS},
 *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_REWIND},
 *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY},
 *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY_PAUSE},
 *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PAUSE},
 *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_STOP},
 *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_FAST_FORWARD},
 *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_NEXT}
 */
public void setTransportControlFlags(int transportControlFlags) {
    if (sHasRemoteControlAPIs) {
        try {
            sRCCSetTransportControlFlags.invoke(mActualRemoteControlClient,
                    transportControlFlags);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

public final Object getActualRemoteControlClientObject() {
    return mActualRemoteControlClient;
}
}

RemoteControlHelper类:

public class RemoteControlHelper {
private static final String TAG = "RemoteControlHelper";

private static boolean sHasRemoteControlAPIs = false;

private static Method sRegisterRemoteControlClientMethod;
private static Method sUnregisterRemoteControlClientMethod;

static {
    try {
        ClassLoader classLoader = RemoteControlHelper.class.getClassLoader();
        Class sRemoteControlClientClass =
                RemoteControlClientCompat.getActualRemoteControlClientClass(classLoader);
        sRegisterRemoteControlClientMethod = AudioManager.class.getMethod(
                "registerRemoteControlClient", new Class[]{sRemoteControlClientClass});
        sUnregisterRemoteControlClientMethod = AudioManager.class.getMethod(
                "unregisterRemoteControlClient", new Class[]{sRemoteControlClientClass});
        sHasRemoteControlAPIs = true;
    } catch (ClassNotFoundException e) {
        // Silently fail when running on an OS before ICS.
    } catch (NoSuchMethodException e) {
        // Silently fail when running on an OS before ICS.
    } catch (IllegalArgumentException e) {
        // Silently fail when running on an OS before ICS.
    } catch (SecurityException e) {
        // Silently fail when running on an OS before ICS.
    }
}

public static void registerRemoteControlClient(AudioManager audioManager,
                                               RemoteControlClientCompat remoteControlClient) {
    if (!sHasRemoteControlAPIs) {
        return;
    }

    try {
        sRegisterRemoteControlClientMethod.invoke(audioManager,
                remoteControlClient.getActualRemoteControlClientObject());
    } catch (Exception e) {
        Log.e(TAG, e.getMessage(), e);
    }
}


public static void unregisterRemoteControlClient(AudioManager audioManager,
                                                 RemoteControlClientCompat remoteControlClient) {
    if (!sHasRemoteControlAPIs) {
        return;
    }

    try {
        sUnregisterRemoteControlClientMethod.invoke(audioManager,
                remoteControlClient.getActualRemoteControlClientObject());
    } catch (Exception e) {
        Log.e(TAG, e.getMessage(), e);
    }
}
}

更改lockPlayer状态dinamicaly:

 private void lockontrolsPlay() {
        if (mRemoteControlClientCompat != null) {
            mRemoteControlClientCompat
                    .setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
        }
    }

    private void lockontrolsPause() {
        if (mRemoteControlClientCompat != null) {
            mRemoteControlClientCompat
                    .setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
        }
    }

接收者:

public class MusicIntentReceiver extends WakefulBroadcastReceiver {
private int headsetSwitch = 1;


@Override
public void onReceive(Context context, Intent intent) {


    if (intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {


        Toast.makeText(context, MyApplication.getContext().getResources().getString (R.string.aptxt15), Toast.LENGTH_SHORT).show();
        intent = new Intent(context, ReproductorDialog.ServicioCanciones.class);
        intent.putExtra("do_action", "pause_cascos");
        context.startService(intent);


    } else if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON)) {

        KeyEvent keyEvent = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
        if (keyEvent.getAction() != KeyEvent.ACTION_DOWN)
            return;

        switch (keyEvent.getKeyCode()) {
            case KeyEvent.KEYCODE_HEADSETHOOK:


            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
                intent = new Intent(context, ReproductorDialog.ServicioCanciones.class);
                intent.putExtra("do_action", "pause");
                context.startService(intent);
                //   context.startService(new Intent(MusicService.ACTION_TOGGLE_PLAYBACK));
                break;
            case KeyEvent.KEYCODE_MEDIA_PLAY:
                //  context.startService(new Intent(MusicService.ACTION_PLAY));
                intent = new Intent(context, ReproductorDialog.ServicioCanciones.class);
                intent.putExtra("do_action", "pause");
                context.startService(intent);
                break;
            case KeyEvent.KEYCODE_MEDIA_PAUSE:
                //  context.startService(new Intent(MusicService.ACTION_PAUSE));
                intent = new Intent(context, ReproductorDialog.ServicioCanciones.class);
                intent.putExtra("do_action", "pause");
                context.startService(intent);
                break;
            case KeyEvent.KEYCODE_MEDIA_STOP:
                //  context.startService(new Intent(MusicService.ACTION_STOP));
                break;
            case KeyEvent.KEYCODE_MEDIA_NEXT:
                intent = new Intent(context, ReproductorDialog.ServicioCanciones.class);
                intent.putExtra("do_action", "next");
                context.startService(intent);
                break;
            case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
                // TODO: ensure that doing this in rapid succession actually plays the
                // previous song
                //   context.startService(new Intent(MusicService.ACTION_REWIND));
                intent = new Intent(context, ReproductorDialog.ServicioCanciones.class);
                intent.putExtra("do_action", "previous");
                context.startService(intent);
                break;
        }
    }
}
}

mAUdioManager是一个AudioManager对象,mMediaButtonReceiverComponent是一个ComponentName,只需添加

AudioManager mAudioManager; 
ComponentName mMediaButtonReceiverComponent;

在调用方法lockScreenControls()之前。这些类在API 18上