package com.minamp; import android.app.Notification; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.content.ComponentName; 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.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; import android.os.StrictMode; import android.view.ViewConfiguration; import com.minamp.plex.Item; /** * What differs between Plexamp Lite's builds, for Android 4.1 and later (Xperia J, Galaxy S II...): ExoPlayer * (gapless, steadier buffering), a notification with art and buttons, lock-screen controls, a resizable widget, * and metered networks. platform/api8 has the same methods for Android 2.2; keep the two in step. */ final class Platform { private Platform() {} static final int API = 16; static final int ART_PX = 320; // the lock screen shows it large static void init(App app) { // The shared code sometimes closes a connection or cuts a download short on the main thread (always brief). // Android 2.x allows that; Android 3+ kills apps that target it. Allow it here too. StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(StrictMode.getThreadPolicy()).permitNetwork().build()); } static Engine newEngine(Context c, boolean fallback) { return fallback ? new MediaPlayerEngine(c) : new ExoEngine(c); } /** Android 4.1 knows which networks cost: mobile data, and a Wi-Fi that is a phone's hotspot. */ static boolean metered(Context c) { try { ConnectivityManager cm = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo ni = cm.getActiveNetworkInfo(); return ni != null && ni.isConnected() && cm.isActiveNetworkMetered(); } catch (Exception e) { return false; } } static boolean needsMenuButton(Context c) { return !ViewConfiguration.get(c).hasPermanentMenuKey(); } /** Art, title and artist with ⏮ ⏯ ⏭ and ×; pulled down, a larger view. Swiping it away while paused stops. */ static Notification playerNotification(Context c, Item it, Bitmap art, int state) { boolean playing = state == PlayerService.PLAYING || state == PlayerService.BUFFERING; Notification n = new Notification.Builder(c) .setSmallIcon(R.drawable.ic_stat) .setContent(PlayerViews.build(c, R.layout.widget_player, it, art, state, true)) .setContentIntent(PlayerViews.open(c)) .setDeleteIntent(PlayerViews.service(c, PlayerService.ACTION_STOP, 14)) .setOngoing(playing) .setWhen(0) .setPriority(playing ? Notification.PRIORITY_HIGH : Notification.PRIORITY_DEFAULT) .build(); n.bigContentView = PlayerViews.build(c, R.layout.widget_player_large, it, art, state, true); return n; } private static RemoteControlClient remote; private static String remoteKey; private static Bitmap remoteArt; /** The lock screen's player: art, title, artist and buttons (they arrive as media buttons). */ static void lockScreen(Context c, Item it, Bitmap art, int state) { AudioManager am = (AudioManager) c.getSystemService(Context.AUDIO_SERVICE); if (it == null || state == PlayerService.IDLE) { if (remote != null) { remote.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); am.unregisterRemoteControlClient(remote); remote = null; remoteKey = null; remoteArt = null; } return; } if (remote == null) { ComponentName buttons = new ComponentName(c, MediaButtonReceiver.class); am.registerMediaButtonEventReceiver(buttons); Intent i = new Intent(Intent.ACTION_MEDIA_BUTTON).setComponent(buttons); remote = new RemoteControlClient(PendingIntent.getBroadcast(c.getApplicationContext(), 0, i, 0)); remote.setTransportControlFlags(RemoteControlClient.FLAG_KEY_MEDIA_PLAY | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE | RemoteControlClient.FLAG_KEY_MEDIA_NEXT | RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS); am.registerRemoteControlClient(remote); } remote.setPlaybackState(state == PlayerService.PLAYING ? RemoteControlClient.PLAYSTATE_PLAYING : state == PlayerService.PAUSED ? RemoteControlClient.PLAYSTATE_PAUSED : RemoteControlClient.PLAYSTATE_BUFFERING); if (it.ratingKey.equals(remoteKey) && art == remoteArt) return; remoteKey = it.ratingKey; remoteArt = art; RemoteControlClient.MetadataEditor ed = remote.editMetadata(true); ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, it.title); ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, it.artist); ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, it.album); ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, it.duration); // a copy: Android recycles the artwork it's given when the next one comes, and this one is in the art cache if (art != null) ed.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, art.copy(Bitmap.Config.ARGB_8888, false)); ed.apply(); } /** Two rows tall or more: the larger layout. */ static int widgetLayout(AppWidgetManager m, int id) { Bundle o = m.getAppWidgetOptions(id); int h = o == null ? 0 : o.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT); return h >= 100 ? R.layout.widget_player_large : R.layout.widget_player; } }