/* * Copyright 2011 Harleen Sahni * * 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.harleensahni.android.mbr; import java.util.Arrays; import java.util.List; import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.SystemClock; import android.preference.PreferenceManager; import android.text.Html; import android.text.Spanned; import android.util.Log; import android.view.KeyEvent; /** * Utility class. * * @author Harleen Sahni */ public final class Utils { private static final String TAG = "MediaButtonRouter"; // private static final String GOOGLE_MUSIC_RECEIVER = "com.google.blahdfdf"; public static final int KEYCODE_MEDIA_PLAY = 126; public static final int KEYCODE_MEDIA_PAUSE = 127; public static final int ICS_API_LEVEL = 14; /** * Prevent instantiation. */ private Utils() { // Intentionally blank } /** * Whether the keyCode represents a media button that we handle. * * @param keyCode * @return */ public static boolean isMediaButton(int keyCode) { return keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS || keyCode == KeyEvent.KEYCODE_MEDIA_REWIND || keyCode == KeyEvent.KEYCODE_MEDIA_STOP || keyCode == KEYCODE_MEDIA_PLAY || keyCode == KEYCODE_MEDIA_PAUSE || keyCode == KeyEvent.KEYCODE_HEADSETHOOK; } /** * Forwards {@code keyCode} to receiver specified as two key events, one for * up and one for down. Optionally launches the application for the * receiver. * * @param context * @param selectedReceiver * @param launch * @param keyCode * @param cleanUpReceiver */ public static void forwardKeyCodeToComponent(Context context, ComponentName selectedReceiver, boolean launch, int keyCode, BroadcastReceiver cleanUpReceiver) { Intent mediaButtonDownIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); KeyEvent downKe = new KeyEvent(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), KeyEvent.ACTION_DOWN, keyCode, 0); mediaButtonDownIntent.putExtra(Intent.EXTRA_KEY_EVENT, downKe); Intent mediaButtonUpIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); KeyEvent upKe = new KeyEvent(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), KeyEvent.ACTION_UP, keyCode, 0); mediaButtonUpIntent.putExtra(Intent.EXTRA_KEY_EVENT, upKe); mediaButtonDownIntent.setComponent(selectedReceiver); mediaButtonUpIntent.setComponent(selectedReceiver); /* COMMENTED OUT FOR MARKET RELEASE Log.i(TAG, "Forwarding Down and Up intent events to " + selectedReceiver + " Down Intent: " + mediaButtonDownIntent + " Down key:" + downKe + " Up Intent: " + mediaButtonUpIntent + " Up key:" + upKe); */ // We start the selected application because some apps broadcast // receivers won't do anything with the intents unless the // application is open. (This this is only if the app isn't // playing music and you want it to play music now) // XXX Is that true? recheck.. // Another reason to launch the app is that if the app does // AudioManager#registerMediaButtonEventReceiver // on load, and we are unable to tell when this app is playing music, // android's default behavior should be correct. if (launch) { Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage( selectedReceiver.getPackageName()); if (launchIntent != null) { context.startActivity(launchIntent); } } context.sendOrderedBroadcast(mediaButtonDownIntent, null, cleanUpReceiver, null, Activity.RESULT_OK, null, null); context.sendOrderedBroadcast(mediaButtonUpIntent, null, cleanUpReceiver, null, Activity.RESULT_OK, null, null); } /** * Gets the list of available media receivers, optionally filtering out ones * the user has indicated should be hidden in preferences. * * @param packageManager * The {@code PackageManager} used to retrieve media button * receivers. * * @param filterHidden * Whether user-hidden media receivers should be shown. * @return The list of {@code ResolveInfo} for different media button * receivers. */ public static List<ResolveInfo> getMediaReceivers(PackageManager packageManager, boolean filterHidden, Context context) { Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); List<ResolveInfo> mediaReceivers = packageManager.queryBroadcastReceivers(mediaButtonIntent, PackageManager.GET_INTENT_FILTERS | PackageManager.GET_RESOLVED_FILTER); if (filterHidden) { String hiddenReceiverIdsString = PreferenceManager.getDefaultSharedPreferences(context).getString( Constants.HIDDEN_APPS_KEY, ""); List<String> hiddenIds = Arrays.asList(hiddenReceiverIdsString.split(",")); for (int i = mediaReceivers.size() - 1; i >= 0; i--) { ResolveInfo mediaReceiverResolveInfo = mediaReceivers.get(i); if (hiddenIds.contains(getMediaReceiverUniqueID(mediaReceiverResolveInfo, packageManager))) { mediaReceivers.remove(i); } } } return mediaReceivers; } public static String getMediaReceiverUniqueID(ResolveInfo resolveInfo, PackageManager packageManager) { String receiverId = resolveInfo.activityInfo.name; // Don't think the following is an issue anymore with the latest versions of google play, if it is i'll add back // if (GOOGLE_MUSIC_RECEIVER.contains(receiverId)) { // // i have to be more exact than just application name because // // the two versions (old and new) of google music // // have the same classnames for their intent receivers. I need // // to know where their apks live to be able to differentiate. // receiverId = resolveInfo.activityInfo.applicationInfo.sourceDir + receiverId; // } return receiverId; } /** * Returns the name of the application of the broadcast receiver specified * by {@code resolveInfo}. * * @param resolveInfo * The receiver. * @return The name of the application. */ public static String getAppName(ResolveInfo resolveInfo, PackageManager packageManager) { return resolveInfo.activityInfo.applicationInfo.loadLabel(packageManager).toString(); } public static AlertDialog showIntroifNeccessary(Context context) { final SharedPreferences preferenceManager = PreferenceManager.getDefaultSharedPreferences(context); if (!preferenceManager.getBoolean(Constants.INTRO_SHOWN_KEY, false)) { // TextView textview = new TextView(context); // textview.setText(context.getText(R.string.intro_text)); Spanned s = Html.fromHtml(context.getString(R.string.intro_text)); Builder alertDialog = new AlertDialog.Builder(context).setTitle("Introduction").setMessage(s); alertDialog.setOnCancelListener(new OnCancelListener() { public void onCancel(DialogInterface dialog) { // preferenceManager.edit().putBoolean(Constants.INTRO_SHOWN_KEY, // true); Log.d(TAG, "Intro cancelled. will show again."); } }); alertDialog.setNegativeButton("Close", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { preferenceManager.edit().putBoolean(Constants.INTRO_SHOWN_KEY, true).commit(); Log.d(TAG, "Intro closed. Will not show again."); } }); return alertDialog.show(); } return null; } public static int getAdjustedKeyCode(KeyEvent keyEvent) { int keyCode = keyEvent.getKeyCode(); if (keyCode == KEYCODE_MEDIA_PLAY || keyCode == KEYCODE_MEDIA_PAUSE) { keyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE; } return keyCode; } /** * Whether we have to go through AudioManager's register media button * receiver where this is only a single media button receiver. See ticket * #10. * * @return */ public static boolean isHandlingThroughSoleReceiver() { return android.os.Build.VERSION.SDK_INT >= ICS_API_LEVEL; } }