package com.miniabs; import android.app.Notification; import android.app.PendingIntent; 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.StrictMode; import android.view.View; import android.view.ViewConfiguration; import android.widget.RemoteViews; /** * What differs between Podlite's builds, for Android 4.1 and later (Xperia J, Galaxy S II...): ExoPlayer (speed, * skipping silences), a notification with the cover and buttons, lock-screen controls, metered networks, and the * ⋮ button on phones without a Menu key. 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 boolean SPEED = true; static final int COVER_PX = 320; // the lock screen shows it large private static boolean initDone; static void init(Context c) { if (initDone) return; initDone = true; // Shared code sometimes closes a connection 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 unmetered(Context c) { try { ConnectivityManager cm = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo ni = cm == null ? null : cm.getActiveNetworkInfo(); return ni != null && ni.isConnected() && !cm.isActiveNetworkMetered(); } catch (Exception e) { return false; } } static boolean needsMenuButton(Context c) { return !ViewConfiguration.get(c).hasPermanentMenuKey(); } /** Cover, title and show with −15 ⏯ +30 and ✕ (pulled down, a larger one). Swiping it away while paused stops. */ static Notification playerNotification(Context c, Episode ep, String what, Bitmap cover, int state, boolean ongoing) { Notification n = new Notification.Builder(c) .setSmallIcon(R.drawable.ic_stat) .setContent(views(c, R.layout.notif_player, ep, what, cover, state)) .setContentIntent(openPlayer(c)) .setDeleteIntent(service(c, PlayerService.A_STOP, 14)) .setOngoing(ongoing) .setWhen(0) .setPriority(state == PlayerService.PLAYING ? Notification.PRIORITY_HIGH : Notification.PRIORITY_DEFAULT) .build(); n.bigContentView = views(c, R.layout.notif_player_big, ep, what, cover, state); return n; } private static Bitmap small, smallOf; private static RemoteViews views(Context c, int layout, Episode ep, String what, Bitmap cover, int state) { RemoteViews v = new RemoteViews(c.getPackageName(), layout); v.setTextViewText(R.id.n_title, ep.title); v.setTextViewText(R.id.n_sub, Util.join(what, ep.show)); if (cover != null) { // the size the notification shows it at: a full-size bitmap would be copied to the status bar each time if (cover != smallOf) { int px = (int) (112 * c.getResources().getDisplayMetrics().density); smallOf = cover; small = cover.getWidth() <= px ? cover : Bitmap.createScaledBitmap(cover, px, px * cover.getHeight() / cover.getWidth(), true); } v.setImageViewBitmap(R.id.n_cover, small); } Prefs p = Prefs.get(c); v.setTextViewText(R.id.n_back, "−" + p.skipBack()); v.setTextViewText(R.id.n_fwd, "+" + p.skipFwd()); boolean playing = state == PlayerService.PLAYING || state == PlayerService.LOADING; v.setImageViewResource(R.id.n_play, playing ? R.drawable.ic_pause_small : R.drawable.ic_play_small); v.setOnClickPendingIntent(R.id.n_back, service(c, PlayerService.A_BACK, 11)); v.setOnClickPendingIntent(R.id.n_play, service(c, PlayerService.A_TOGGLE, 12)); v.setOnClickPendingIntent(R.id.n_fwd, service(c, PlayerService.A_FWD, 13)); v.setOnClickPendingIntent(R.id.n_close, service(c, PlayerService.A_STOP, 14)); v.setOnClickPendingIntent(R.id.n_cover, openPlayer(c)); v.setOnClickPendingIntent(R.id.n_text, openPlayer(c)); v.setViewVisibility(R.id.n_close, View.VISIBLE); return v; } static PendingIntent openPlayer(Context c) { Intent i = new Intent(c, PlayerActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return PendingIntent.getActivity(c, 0, i, PendingIntent.FLAG_UPDATE_CURRENT); } private static PendingIntent service(Context c, String action, int request) { return PendingIntent.getService(c, request, new Intent(c, PlayerService.class).setAction(action), 0); } private static RemoteControlClient remote; private static String remoteFor; private static Bitmap remoteCover; /** The lock screen's player: cover, title, show and buttons (⏮ ⏭ skip back and forward: media keys). */ static void lockScreen(Context c, Episode ep, Bitmap cover, int state) { AudioManager am = (AudioManager) c.getSystemService(Context.AUDIO_SERVICE); if (ep == null || state == PlayerService.IDLE) { if (remote != null) { remote.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); am.unregisterRemoteControlClient(remote); remote = null; remoteFor = null; remoteCover = 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.LOADING ? RemoteControlClient.PLAYSTATE_BUFFERING : state == PlayerService.ERROR ? RemoteControlClient.PLAYSTATE_ERROR : RemoteControlClient.PLAYSTATE_PAUSED); if (ep.id.equals(remoteFor) && cover == remoteCover) return; remoteFor = ep.id; remoteCover = cover; RemoteControlClient.MetadataEditor ed = remote.editMetadata(true); ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, ep.title); ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, ep.show); ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, (long) (ep.duration * 1000)); // a copy: Android recycles the artwork it's given when the next one comes, and this one is in the cover cache if (cover != null) ed.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, cover.copy(Bitmap.Config.ARGB_8888, false)); ed.apply(); } }