Bug 1264901 - Implement media-control front-end. r=ahunt, a=lizzard
authorSebastian Kaspari <s.kaspari@gmail.com>
Fri, 03 Jun 2016 14:43:21 +0200
changeset 339642 81216130eacc2d0e96a4e6a19119cea6e7d0507f
parent 339641 dd0fbfbe790f880c80f126916e1c4e38436f9ad6
child 339643 d7a911ef57ad6b836046532794014cc06d24c6f2
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersahunt, lizzard
bugs1264901
milestone49.0a2
Bug 1264901 - Implement media-control front-end. r=ahunt, a=lizzard MozReview-Commit-ID: H6Py4Wd35db
mobile/android/app/mobile.js
mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
mobile/android/base/resources/drawable-hdpi/ic_media_pause.png
mobile/android/base/resources/drawable-hdpi/ic_media_play.png
mobile/android/base/resources/drawable-hdpi/notification_media.png
mobile/android/base/resources/drawable-xhdpi/ic_media_pause.png
mobile/android/base/resources/drawable-xhdpi/ic_media_play.png
mobile/android/base/resources/drawable-xhdpi/notification_media.png
mobile/android/base/resources/drawable-xxhdpi/ic_media_pause.png
mobile/android/base/resources/drawable-xxhdpi/ic_media_play.png
mobile/android/base/resources/drawable-xxhdpi/notification_media.png
mobile/android/base/resources/values/dimens.xml
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -919,10 +919,9 @@ pref("identity.fxaccounts.remote.oauth.u
 // Token server used by Firefox Account-authenticated Sync.
 pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sync/1.5");
 
 // Enable Presentation API
 pref("dom.presentation.enabled", true);
 pref("dom.presentation.discovery.enabled", true);
 
 pref("dom.audiochannel.audioCompeting", true);
-// TODO : turn this pref default on in bug1264901
-pref("dom.audiochannel.mediaControl", false);
+pref("dom.audiochannel.mediaControl", true);
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
@@ -1,32 +1,38 @@
 package org.mozilla.gecko.media;
 
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoEvent;
-import org.mozilla.gecko.PrefsHelper;
-
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
-import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.support.v4.app.NotificationManagerCompat;
 import android.util.Log;
 
-public class MediaControlService extends Service {
+import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.PrefsHelper;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.Tab;
+import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import java.lang.ref.WeakReference;
+
+public class MediaControlService extends Service implements Tabs.OnTabsChangedListener {
     private static final String LOGTAG = "MediaControlService";
 
     public static final String ACTION_START          = "action_start";
     public static final String ACTION_PLAY           = "action_play";
     public static final String ACTION_PAUSE          = "action_pause";
     public static final String ACTION_STOP           = "action_stop";
     public static final String ACTION_REMOVE_CONTROL = "action_remove_control";
 
@@ -39,26 +45,38 @@ public class MediaControlService extends
     private MediaController mController;
 
     private PrefsHelper.PrefHandler mPrefsObserver;
     private final String[] mPrefs = { MEDIA_CONTROL_PREF };
 
     private boolean mIsInitMediaSession = false;
     private boolean mIsMediaControlPrefOn = true;
 
+    private static WeakReference<Tab> mTabReference = null;
+
+    private int coverSize;
+
     @Override
     public void onCreate() {
+        mTabReference = new WeakReference<>(null);
+
         getGeckoPreference();
         initMediaSession();
+
+        coverSize = (int) getResources().getDimension(R.dimen.notification_media_cover);
+
+        Tabs.registerOnTabsChangedListener(this);
     }
 
     @Override
     public void onDestroy() {
         notifyControlInterfaceChanged(ACTION_REMOVE_CONTROL);
         PrefsHelper.removeObserver(mPrefsObserver);
+
+        Tabs.unregisterOnTabsChangedListener(this);
     }
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         handleIntent(intent);
         return super.onStartCommand(intent, flags, startId);
     }
 
@@ -73,16 +91,32 @@ public class MediaControlService extends
         return super.onUnbind(intent);
     }
 
     @Override
     public void onTaskRemoved(Intent rootIntent) {
         stopSelf();
     }
 
+    @Override
+    public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
+        if (!mIsInitMediaSession) {
+            return;
+        }
+
+        if (tab == mTabReference.get()) {
+            return;
+        }
+
+        if (msg == Tabs.TabEvents.AUDIO_PLAYING_CHANGE && tab.isAudioPlaying()) {
+            mTabReference = new WeakReference<Tab>(tab);
+            notifyControlInterfaceChanged(ACTION_PAUSE);
+        }
+    }
+
     private boolean isAndroidVersionLollopopOrHigher() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
     }
 
     private void handleIntent(Intent intent) {
         if (intent == null || intent.getAction() == null ||
            !mIsInitMediaSession) {
             return;
@@ -196,88 +230,117 @@ public class MediaControlService extends
         GeckoAppShell.notifyObservers(topic, data);
     }
 
     private boolean isNeedToRemoveControlInterface(String action) {
         return (action.equals(ACTION_STOP) ||
                 action.equals(ACTION_REMOVE_CONTROL));
     }
 
-    private void notifyControlInterfaceChanged(String action) {
+    private void notifyControlInterfaceChanged(final String action) {
         Log.d(LOGTAG, "notifyControlInterfaceChanged, action = " + action);
-        NotificationManager notificationManager = (NotificationManager)
-            getSystemService(Context.NOTIFICATION_SERVICE);
 
         if (isNeedToRemoveControlInterface(action)) {
-            notificationManager.cancel(MEDIA_CONTROL_ID);
+            NotificationManagerCompat.from(this).cancel(MEDIA_CONTROL_ID);
             return;
         }
 
         if (!mIsMediaControlPrefOn) {
             return;
         }
 
-        notificationManager.notify(MEDIA_CONTROL_ID, getNotification(action));
+        final Tab tab = mTabReference.get();
+
+        if (tab == null) {
+            return;
+        }
+
+        ThreadUtils.postToBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                updateNotification(tab, action);
+            }
+        });
     }
 
-    private Notification getNotification(String action) {
-        // TODO : use website name, content and favicon in bug1264901.
-        return new Notification.Builder(this)
-            .setSmallIcon(android.R.drawable.ic_media_play)
-            .setContentTitle("Media Title")
-            .setContentText("Media Artist")
-            .setDeleteIntent(getDeletePendingIntent())
-            .setContentIntent(getClickPendingIntent())
-            .setStyle(getMediaStyle())
-            .addAction(getAction(action))
+    private void updateNotification(Tab tab, String action) {
+        ThreadUtils.assertNotOnUiThread();
+
+        final Notification.MediaStyle style = new Notification.MediaStyle();
+        style.setShowActionsInCompactView(0);
+
+        final Notification notification = new Notification.Builder(this)
+            .setSmallIcon(R.drawable.flat_icon)
+            .setLargeIcon(generateCoverArt(tab))
+            .setContentTitle(tab.getTitle())
+            .setContentText(tab.getURL())
+            .setContentIntent(createContentIntent())
+            .setDeleteIntent(createDeleteIntent())
+            .setStyle(style)
+            .addAction(createNotificationAction(action))
             .setOngoing(action.equals(ACTION_PAUSE))
+            .setShowWhen(false)
+            .setWhen(0)
             .build();
+
+        NotificationManagerCompat.from(this)
+                .notify(MEDIA_CONTROL_ID, notification);
     }
 
-    private Notification.Action getAction(String action) {
-        int icon = getActionIcon(action);
-        String title = getActionTitle(action);
+    private Notification.Action createNotificationAction(String action) {
+        boolean isPlayAction = action.equals(ACTION_PLAY);
+
+        int icon = isPlayAction ? R.drawable.ic_media_play : R.drawable.ic_media_pause;
+        String title = getString(isPlayAction ? R.string.media_pause : R.string.media_pause);
 
-        Intent intent = new Intent(getApplicationContext(), MediaControlService.class);
+        final Intent intent = new Intent(getApplicationContext(), MediaControlService.class);
         intent.setAction(action);
-        PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 1, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 1, intent, 0);
+
+        //noinspection deprecation - The new constructor is only for API > 23
         return new Notification.Action.Builder(icon, title, pendingIntent).build();
     }
 
-    private int getActionIcon(String action) {
-        switch (action) {
-            case ACTION_PLAY :
-                return android.R.drawable.ic_media_play;
-            case ACTION_PAUSE :
-                return android.R.drawable.ic_media_pause;
-            default:
-                return 0;
-        }
-    }
-
-    private String getActionTitle(String action) {
-        switch (action) {
-            case ACTION_PLAY :
-                return "Play";
-            case ACTION_PAUSE :
-                return "Pause";
-            default:
-                return null;
-        }
-    }
-
-    private PendingIntent getDeletePendingIntent() {
-        Intent intent = new Intent(getApplicationContext(), MediaControlService.class);
-        intent.setAction(ACTION_REMOVE_CONTROL);
-        return PendingIntent.getService(getApplicationContext(), 1, intent, 0);
-    }
-
-    private PendingIntent getClickPendingIntent() {
+    private PendingIntent createContentIntent() {
         Intent intent = new Intent(getApplicationContext(), BrowserApp.class);
         return PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
     }
 
-    private Notification.MediaStyle getMediaStyle() {
-        Notification.MediaStyle style = new Notification.MediaStyle();
-        style.setShowActionsInCompactView(0);
-        return style;
+    private PendingIntent createDeleteIntent() {
+        Intent intent = new Intent(getApplicationContext(), MediaControlService.class);
+        intent.setAction(ACTION_REMOVE_CONTROL);
+        return  PendingIntent.getService(getApplicationContext(), 1, intent, 0);
+    }
+
+    private Bitmap generateCoverArt(Tab tab) {
+        final Bitmap favicon = tab.getFavicon();
+
+        // If we do not have a favicon or if it's smaller than 72 pixels then just use the default icon.
+        if (favicon == null || favicon.getWidth() < 72 || favicon.getHeight() < 72) {
+            // Use the launcher icon as fallback
+            return BitmapFactory.decodeResource(getResources(), R.drawable.notification_media);
+        }
+
+        // Favicon should at least have half of the size of the cover
+        int width = Math.max(favicon.getWidth(), coverSize / 2);
+        int height = Math.max(favicon.getHeight(), coverSize / 2);
+
+        final Bitmap coverArt = Bitmap.createBitmap(coverSize, coverSize, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(coverArt);
+        canvas.drawColor(0xFF777777);
+
+        int left = Math.max(0, (coverArt.getWidth() / 2) - (width / 2));
+        int right = Math.min(coverSize, left + width);
+        int top = Math.max(0, (coverArt.getHeight() / 2) - (height / 2));
+        int bottom = Math.min(coverSize, top + height);
+
+        final Paint paint = new Paint();
+        paint.setAntiAlias(true);
+
+        canvas.drawBitmap(favicon,
+                new Rect(0, 0, favicon.getWidth(), favicon.getHeight()),
+                new Rect(left, top, right, bottom),
+                paint);
+
+        return coverArt;
+
     }
 }
\ No newline at end of file
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1701f34b01c3f6fdab2a6a1b58d7ab0224d91295
GIT binary patch
literal 103
zc%17D@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;o~MgrNCjiE1LNWZkw5tk4mFQj
xc%7Ikj=apju)=XMTd$N!7LYMT{2x%BfkFHwlX~y3uO&d844$rjF6*2UngGUE9ZCQI
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f77ad6b57b7168dfe5a49a0ba455eac2e7e58a0a
GIT binary patch
literal 194
zc%17D@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8r>Bc!NCo5DDF+!D19?~<K3l`6
z`os1`lh%qRy)e<P8$w~s)n!eL?hMQ)L?)<IzF#UNqo@$sD%O)B(aNx_WR4R<@+?0^
zjhB|K3~F2EI2j~6+|^*2FnNyC8{b5cU3W$1q-Jw14@eWy>bWY$)0_N!3$OFHmGvGj
qUc1~fj34)k982CEn(^=_`$4IwCxutdiZ*~;$l&Sf=d#Wzp$Pz=#6ji&
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c85e32c01989d27652abdc114c48b300d2cc2c4f
GIT binary patch
literal 621
zc%17D@N?(olHy`uVBq!ia0vp^2SAtuNHCOdH@?llz+~d-;uuoF`1V$FacrPS>&JiB
z)wkv*2i)G4TO4rv+1b47E2VAsuX(xM{k2M^XPxoyd8_X#&lG1M3pC_ktv`MDd-?Mp
zOw0E5bZ9Kw#S~W87xUbfac|#|{;E_?i6Z5?u!kH0zyE95H{7}}x!{{Q+ls$)8MErs
zA>8*|29Gk%=Wz%8E@zeasB>;xNpQoh{oD=z{&y_CD-4pq@Y-|5H#2#I&!1LjM8`5+
znxquMJ&X6oHK{;Bg|#Q<*BI~E*)Vf$pr*oF?yH^8nDks(8w8_+*Jb}oGF-Q^Vfs_%
zE&pZCBwkUk{aO>u7-*;Pe*W*`JImIKIqzj)p8wRwd*^lUqq%VnTTGv;*G~DaX=lK^
zgm>YsRTh@V7p66ogm$Z2+J(hDW0Q!>Fbyr-eyPsfVb`D6_v03noaNi#vHG-E^3-`Z
z7}YXL<1;5*%C>f>UwcpcYtXMh`x!G7`5v8)dYiqww@B%6(4hxOeWzbG`rpmZaI?v+
z_RcH|{=em|@80XlQy=Eaul|s5`rAb%{_?-_>%)IFCGRM^y!hDq87daHlm1T1|8X&H
zPuf%0?VndTUcdS}!^I%)@PWy%Pe?k~=km31ziVE9`HXx2bE7}JWJO{^R6OGw6_@3U
SH*D$#@jYGrT-G@yGywq2?D(nx
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f49aed757118a941b567629ec217cde1aaf257e8
GIT binary patch
literal 90
zc%17D@N?(olHy`uVBq!ia0vp^1|ZA`BpB)|k7xlYrjj7PU<QV=$!9HqJYi24$B+uf
k<OM<x{vY_Cm=S!1k)ch%{N(L`^Zg)Ap00i_>zopr0A<G){{R30
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9cc777c2c44a05f4bce98104d54d7116b8508785
GIT binary patch
literal 214
zc%17D@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0Dxt=bLAr*{ouX!^v7K*qPzJJOx
zp@ApJ!;w4af>Y<q1&w=`e&6^<NalPs>yNWDKG&B_sF!CH32<PvKBmXuKCQ%;VL|FE
zrh>GyCm4;Y)AShlx0&!wur%1-;PYr|!;#gDo#8A(>)BM|1U>ffO{mx}QL$m+&rAI^
zX{l+qj10GTvBuVK{=Mq0m1EuGi8B>M6&$S`nzpR#`(L+PZ$SgoyoJlPLKVb%T!9W{
N@O1TaS?83{1OVvqPUHXp
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6d9a57966dea46ca479c346c3f124e10192bb73d
GIT binary patch
literal 859
zc%17D@N?(olHy`uVBq!ia0y~yU}OMc4j{=;DfxXD0|T>?r;B4q1>@U0&6{N%1&%)a
zbG`e<wzEk&Hoy1Y*p|DxS-5)DqP@?{yKJky<L}0u^I3n!hJkW0L4s(dZ6<T73pOV=
zXgkFH*SYuP7lVHTqx+-ST{?`%;`_f>hDb5#R0_!(U*uEB@A5ak&g<~@zoGXThAaQ}
zF=W+8Gi=?j-*D@`xWn7`+zY;KXASs0n`y=0$qZH3g&J<zb1r!I=fEw-fZytjS6pMH
zVze2u{;M?XGJhQY>pBZio_B#Cd!Lvo!-8-A%n<_bFNJD0+>6$$TYpiQanoCQ{!sRW
zS6c(>m>u?6Px;-f&1~?>^N(G_nj7+`{;gtoviwmwQ_9ne0cqhZ33-3y8m2ZIr+%=N
zwFwt&cy^yTKs>xH<<y}7v2f;u>-h{9zW#qz-N<ZJwM~klx{kf!UH!VRp{CDT`TVyq
zuHas38@z4Rn{A94oKKI2Ton$n)5?iT_{z;2qZc|q%<?dsLbUiPt*NI&O@A_^95*cr
zy&4euyqBTP{@MkTsnhR?o#&QsSo3>xeeLvB8{HTto0d3U)e7zIW!Q8&zkS}zc>7@c
z8!uQk1Scnd=~=tzB<F&Mm$UyXh|cw6IBl}cA)htm4MT(21C<l06QmU%M?W;Q;hAa2
z#~)mJ?{BMz^7<ncny-FF)y%${{jr}ZT>D32i+^&B%-qMnE9Y<u`AiI47FRk?Woc%S
z_?%@Y4Fe~w+{Afu_Fi>w<Jftu!a6g5RwyOhl;Kv7w0g}jp+G6&r;W76hE}%OD>n4<
zNoz{xFTMJ9*UYG9v*6u^2db?1M$hfL{Zf^M3Mr6iP{F!idGmR{wRMj`JWp3Ymvv4F
FO#r&o8)N_g
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7192ad487eacb4f8f530ebe2878760e2528fbc5f
GIT binary patch
literal 92
zc%17D@N?(olHy`uVBq!ia0vp^9w5vJBp7O^^}Pa8OeH~n!3+##lh0ZJd7_>!jv*C{
m$r5W0{Noqkjk>0hJ%NeAb!~d=tj~okAZ?zmelF{r5}E*xz!xz9
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e0f6a850d17d4b29baca6be83c6f27207841c225
GIT binary patch
literal 279
zc%17D@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw?s>X6hEy=Vy|Hm8V<3Y|qQef>
z6%A4$3%PbJ;M#SeMXp3aoQZD+%Y*|gpYO8O^;_<KKh40JkyRwXfswVOnu#mJ=`uG9
z_pd2)9T<Xd#VJG>E_ukpG~?n&ri1egTZK6i{H}AeXt22{96i9fM53Zmgjsz;(gW5Y
zo;eO(jp81LADA@R{D6|&OC&6SlKMbN<|_H-hHpI@pBC2{{buA{Zm?~RoUHAs${kVr
z>-I=i#3h;*h)-a9$8hOD?f<FgQV;)be16D_xtuZc0iOb|jrjBXmQd#b3HGz;_Z<(E
Smr4MA%HZkh=d#Wzp$Pzz6Jk&R
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9046219e40a73a1857528dd8d31ba1c55edba3db
GIT binary patch
literal 1379
zc$~GAeK6Yx7{{NVA7WCp96^_`IlHT!HFvDu>ZPp-U8__@M@o81T)e+TNr&Hb*L170
zo8D-n3DwOInO42VkqvQICCtf+rbK#_h^xlDM9B2N-R-Wom)jrTe?H%PzV|$TJm0Uv
zLMdjZ_ND+}77`qI768HC$Yc+|7lNgD03(->z+>m~5VaiDI8z8>#YPNYXDx`=M5g#`
zNZLB77gLq}3>k0nq<kT2<&b}5-C>d5nmOf+><2J_0D!$P1RMbjVK4iy@a^uuh5v9Q
z&F-hC4^q{iYQTL_`27~cX9dWgk2^3}ucF~ZJDo)ucLDVBcIi=J4D6dK=e*v0Za8{L
z7u5*vcKV4#I4vn+K>&9(4oM>KB9CnKFjZOvse}Glo6;d*eAS{_y@?ym)uBl~yNEmS
z&$j+7bZ59?B#GV&Ey-Rm1GcSx!Gt{WMDBrl&hWf%RT$E%dfPXSKtYMV1ufA?*J)@q
zXSIlmW$wx)AHo`WS86~W5hg^yN9s*~A91m7{wW9KdS(9F^f2zVC818OCFY;9Fvz=f
zWj(B_$C=xbywUv*u2M_Rreo)uT}3_o49p@c59=t5%k}jh1B(Pa*0GR5y&|llV~b6x
zFjv3KPA*1Bj}@@O|2kgl*LW5G0by390Lj)6EoXT8_QYU0zB!YOWhM&wgbRA<(yNcH
z@Xba0F|F>RoR(8iF~PCIe}f`>>wgzYVW|~5Q9;WX10;32<%K>0?>Y(HJ7N3Yo!`vF
ztz&VpG88}6LT)H~*yY8>2Xx;ZT#G#Ck@$Pi8Uf2&3C@+<Of*E(2)@RSB+sgeJ3o@@
z2WmC<2BANDlEr8mA=<xm3x`z}btbS?VAER?i;~mN_<DCYQ{w~!u4BnCcU!<~98h(g
zrXOLOiPuWmZSD=eCx{uSV{AQxGtbOSaP_m<F{X!8^?n0fMSJZG^mnm0kGH$++fbQ6
z`b3|Uspdt;9Pq`>)<ksM^>oT)6e-ftLrQCCO}^$6j~DCM9gVViqDYnwiA#!_Xzq#(
zfLshuzvMg_3QO<bm9O8dEPJ0vT&^{N8@NrfIaXF%@E$6T`nKm5ZkBPVu+m6;#%jlt
zfbr|LuNq^vcjphE#9iCEVTswc?v#@-+bu3r>CY_Fg^O0hjhrafo&sf#moPC4`Jw$j
z%6<2<`B6X4daLT8WkyOuEooAwouyI;Maq|RHCcJ>N)=_YHOIwRYGm$2@T8p|=hN&l
z+s<F^uR8lVRcyNVNSH6|8zqF!o3s})P0*}M-*fik1fH;kRpW-OA4L9^<`4diK&nUa
zFc_z3l}8^$!c>}JI+7$h@XWM&F{s%a9z8QLkHuFXyX-pbKb;>1Nx!-(t$o^>3K9z6
k{^Yud|56qIL*?w4UQk)P5A^N^ziH2qPeKEQ$LYm?0El(~fdBvi
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -212,9 +212,11 @@
     <dimen name="action_bar_divider_height">2dp</dimen>
 
     <!-- http://blog.danlew.net/2015/01/06/handling-android-resources-with-non-standard-formats/ -->
     <item name="match_parent" type="dimen">-1</item>
     <item name="wrap_content" type="dimen">-2</item>
 
     <item name="tab_strip_content_start" type="dimen">12dp</item>
     <item name="firstrun_tab_strip_content_start" type="dimen">15dp</item>
+
+    <item name="notification_media_cover" type="dimen">128dp</item>
 </resources>