Bug 874401 - Show an ongoing notification when webrtc is active. r=mfinkle
authorWes Johnston <wjohnston@mozilla.com>
Tue, 11 Jun 2013 12:14:43 -0700
changeset 141805 d038d584102c8535594fa6da75dfad2bb81484c5
parent 141804 4e0f0d13afd3c731de78aa74625fa6a38a3d9d98
child 141806 14b4165dc0e28e10ca75988d06a16bfcf2c91c67
push id3911
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 20:17:26 +0000
treeherdermozilla-aurora@7e26ca8db92b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs874401
milestone24.0a1
Bug 874401 - Show an ongoing notification when webrtc is active. r=mfinkle
mobile/android/base/GeckoApp.java
mobile/android/base/Makefile.in
mobile/android/base/NotificationHandler.java
mobile/android/base/NotificationHelper.java
mobile/android/base/gfx/BitmapUtils.java
mobile/android/base/resources/drawable-hdpi-v11/alert_camera.png
mobile/android/base/resources/drawable-hdpi-v11/alert_mic.png
mobile/android/base/resources/drawable-hdpi/alert_camera.png
mobile/android/base/resources/drawable-hdpi/alert_mic.png
mobile/android/base/resources/drawable-hdpi/alert_mic_camera.png
mobile/android/base/resources/drawable-mdpi-v11/alert_camera.png
mobile/android/base/resources/drawable-mdpi-v11/alert_mic.png
mobile/android/base/resources/drawable-mdpi-v11/alert_mic_camera.png
mobile/android/base/resources/drawable-mdpi/alert_camera.png
mobile/android/base/resources/drawable-mdpi/alert_mic.png
mobile/android/base/resources/drawable-mdpi/alert_mic_camera.png
mobile/android/base/resources/drawable-xhdpi-v11/alert_camera.png
mobile/android/base/resources/drawable-xhdpi-v11/alert_mic.png
mobile/android/base/resources/drawable-xhdpi-v11/alert_mic_camera.png
mobile/android/base/resources/drawable-xhdpi/alert_camera.png
mobile/android/base/resources/drawable-xhdpi/alert_mic.png
mobile/android/base/resources/drawable-xhdpi/alert_mic_camera.png
mobile/android/chrome/content/WebrtcUI.js
mobile/android/chrome/content/browser.js
mobile/android/locales/en-US/chrome/browser.properties
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -177,16 +177,19 @@ abstract public class GeckoApp
 
     private PromptService mPromptService;
     private TextSelection mTextSelection;
 
     protected DoorHangerPopup mDoorHangerPopup;
     protected FormAssistPopup mFormAssistPopup;
     protected TabsPanel mTabsPanel;
 
+    // Handles notification messages from javascript
+    protected NotificationHelper mNotificationHelper;
+
     protected LayerView mLayerView;
     private AbsoluteLayout mPluginContainer;
 
     private FullScreenHolder mFullScreenPluginContainer;
     private View mFullScreenPluginView;
 
     private HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
 
@@ -1237,16 +1240,17 @@ abstract public class GeckoApp
         setContentView(getLayout());
 
         // Set up Gecko layout.
         mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
         mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
 
         // Set up tabs panel.
         mTabsPanel = (TabsPanel) findViewById(R.id.tabs_panel);
+        mNotificationHelper = new NotificationHelper(this);
 
         // Check if the last run was exited due to a normal kill while
         // we were in the background, or a more harsh kill while we were
         // active.
         mRestoreMode = getSessionRestoreState(savedInstanceState);
         if (mRestoreMode == RESTORE_OOM) {
             boolean wasInBackground =
                 savedInstanceState.getBoolean(SAVED_STATE_IN_BACKGROUND, false);
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -119,16 +119,17 @@ FENNEC_JAVA_FILES = \
   LightweightTheme.java \
   LightweightThemeDrawable.java \
   LinkPreference.java \
   MemoryMonitor.java \
   MotionEventInterceptor.java \
   MultiChoicePreference.java \
   NotificationClient.java \
   NotificationHandler.java \
+  NotificationHelper.java \
   NotificationService.java \
   NSSBridge.java \
   OrderedBroadcastHelper.java \
   PrefsHelper.java \
   PrivateDataPreference.java \
   PrivateTab.java \
   ProfileMigrator.java \
   Prompt.java \
@@ -591,16 +592,19 @@ RES_DRAWABLE_MDPI = \
   res/drawable-mdpi/abouthome_promo_logo_sync.png \
   res/drawable-mdpi/abouthome_thumbnail.png \
   res/drawable-mdpi/abouthome_thumbnail_bg.png \
   res/drawable-mdpi/abouthome_thumbnail_add.png \
   res/drawable-mdpi/address_bar_bg_shadow.png \
   res/drawable-mdpi/alert_addon.png \
   res/drawable-mdpi/alert_app.png \
   res/drawable-mdpi/alert_download.png \
+  res/drawable-mdpi/alert_camera.png \
+  res/drawable-mdpi/alert_mic.png \
+  res/drawable-mdpi/alert_mic_camera.png \
   res/drawable-mdpi/autocomplete_list_bg.9.png \
   res/drawable-mdpi/awesomebar_tab_center.9.png \
   res/drawable-mdpi/awesomebar_tab_left.9.png \
   res/drawable-mdpi/awesomebar_tab_right.9.png \
   res/drawable-mdpi/awesomebar_sep_left.9.png \
   res/drawable-mdpi/awesomebar_sep_right.9.png \
   res/drawable-mdpi/desktop_notification.png \
   res/drawable-mdpi/ic_addons_empty.png \
@@ -706,16 +710,19 @@ RES_DRAWABLE_HDPI = \
   res/drawable-hdpi/abouthome_promo_logo_sync.png \
   res/drawable-hdpi/abouthome_thumbnail.png \
   res/drawable-hdpi/abouthome_thumbnail_bg.png \
   res/drawable-hdpi/abouthome_thumbnail_add.png \
   res/drawable-hdpi/address_bar_bg_shadow.png \
   res/drawable-hdpi/alert_addon.png \
   res/drawable-hdpi/alert_app.png \
   res/drawable-hdpi/alert_download.png \
+  res/drawable-hdpi/alert_camera.png \
+  res/drawable-hdpi/alert_mic.png \
+  res/drawable-hdpi/alert_mic_camera.png \
   res/drawable-hdpi/awesomebar_tab_center.9.png \
   res/drawable-hdpi/awesomebar_tab_left.9.png \
   res/drawable-hdpi/awesomebar_tab_right.9.png \
   res/drawable-hdpi/awesomebar_sep_left.9.png \
   res/drawable-hdpi/awesomebar_sep_right.9.png \
   res/drawable-hdpi/ic_addons_empty.png \
   res/drawable-hdpi/ic_awesomebar_go.png \
   res/drawable-hdpi/ic_awesomebar_reader.png \
@@ -799,16 +806,19 @@ RES_DRAWABLE_XHDPI = \
   res/drawable-xhdpi/address_bar_bg_shadow.png \
   res/drawable-xhdpi/address_bar_url_default.9.png \
   res/drawable-xhdpi/address_bar_url_default_pb.9.png \
   res/drawable-xhdpi/address_bar_url_pressed.9.png \
   res/drawable-xhdpi/address_bar_url_pressed_pb.9.png \
   res/drawable-xhdpi/alert_addon.png \
   res/drawable-xhdpi/alert_app.png \
   res/drawable-xhdpi/alert_download.png \
+  res/drawable-xhdpi/alert_camera.png \
+  res/drawable-xhdpi/alert_mic.png \
+  res/drawable-xhdpi/alert_mic_camera.png \
   res/drawable-xhdpi/awesomebar_tab_center.9.png \
   res/drawable-xhdpi/awesomebar_tab_left.9.png \
   res/drawable-xhdpi/awesomebar_tab_right.9.png \
   res/drawable-xhdpi/awesomebar_sep_left.9.png \
   res/drawable-xhdpi/awesomebar_sep_right.9.png \
   res/drawable-xhdpi/ic_addons_empty.png \
   res/drawable-xhdpi/ic_awesomebar_go.png \
   res/drawable-xhdpi/ic_awesomebar_reader.png \
@@ -869,16 +879,19 @@ RES_DRAWABLE_XHDPI = \
   res/drawable-xhdpi/handle_middle.png \
   res/drawable-xhdpi/handle_start.png \
   $(NULL)
 
 RES_DRAWABLE_MDPI_V11 = \
   res/drawable-mdpi-v11/alert_addon.png \
   res/drawable-mdpi-v11/alert_app.png \
   res/drawable-mdpi-v11/alert_download.png \
+  res/drawable-mdpi-v11/alert_camera.png \
+  res/drawable-mdpi-v11/alert_mic.png \
+  res/drawable-mdpi-v11/alert_mic_camera.png \
   res/drawable-mdpi-v11/firefox_settings_alert.png \
   res/drawable-mdpi-v11/ic_menu_addons.png \
   res/drawable-mdpi-v11/ic_menu_apps.png \
   res/drawable-mdpi-v11/ic_menu_back.png \
   res/drawable-mdpi-v11/ic_menu_bookmark_add.png \
   res/drawable-mdpi-v11/ic_menu_bookmark_remove.png \
   res/drawable-mdpi-v11/ic_menu_desktop_mode_off.png \
   res/drawable-mdpi-v11/ic_menu_desktop_mode_on.png \
@@ -895,16 +908,19 @@ RES_DRAWABLE_MDPI_V11 = \
   res/drawable-mdpi-v11/ic_menu_quit.png \
   res/drawable-mdpi-v11/ic_status_logo.png \
   $(NULL)
 
 RES_DRAWABLE_HDPI_V11 = \
   res/drawable-hdpi-v11/alert_addon.png \
   res/drawable-hdpi-v11/alert_app.png \
   res/drawable-hdpi-v11/alert_download.png \
+  res/drawable-hdpi-v11/alert_camera.png \
+  res/drawable-hdpi-v11/alert_mic.png \
+  res/drawable-hdpi-v11/alert_mic_camera.png \
   res/drawable-hdpi-v11/firefox_settings_alert.png \
   res/drawable-hdpi-v11/ic_menu_addons.png \
   res/drawable-hdpi-v11/ic_menu_apps.png \
   res/drawable-hdpi-v11/ic_menu_back.png \
   res/drawable-hdpi-v11/ic_menu_bookmark_add.png \
   res/drawable-hdpi-v11/ic_menu_bookmark_remove.png \
   res/drawable-hdpi-v11/ic_menu_desktop_mode_off.png \
   res/drawable-hdpi-v11/ic_menu_desktop_mode_on.png \
@@ -921,16 +937,19 @@ RES_DRAWABLE_HDPI_V11 = \
   res/drawable-hdpi-v11/ic_menu_quit.png \
   res/drawable-hdpi-v11/ic_status_logo.png \
   $(NULL)
 
 RES_DRAWABLE_XHDPI_V11 = \
   res/drawable-xhdpi-v11/alert_addon.png \
   res/drawable-xhdpi-v11/alert_app.png \
   res/drawable-xhdpi-v11/alert_download.png \
+  res/drawable-xhdpi-v11/alert_camera.png \
+  res/drawable-xhdpi-v11/alert_mic.png \
+  res/drawable-xhdpi-v11/alert_mic_camera.png \
   res/drawable-xhdpi-v11/firefox_settings_alert.png \
   res/drawable-xhdpi-v11/ic_menu_addons.png \
   res/drawable-xhdpi-v11/ic_menu_apps.png \
   res/drawable-xhdpi-v11/ic_menu_back.png \
   res/drawable-xhdpi-v11/ic_menu_bookmark_add.png \
   res/drawable-xhdpi-v11/ic_menu_bookmark_remove.png \
   res/drawable-xhdpi-v11/ic_menu_desktop_mode_off.png \
   res/drawable-xhdpi-v11/ic_menu_desktop_mode_on.png \
--- a/mobile/android/base/NotificationHandler.java
+++ b/mobile/android/base/NotificationHandler.java
@@ -1,20 +1,21 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.gfx.BitmapUtils;
+
 import android.app.PendingIntent;
 import android.content.Context;
 import android.net.Uri;
 
-import java.lang.reflect.Field;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class NotificationHandler {
     private final ConcurrentHashMap<Integer, AlertNotification>
             mAlertNotifications = new ConcurrentHashMap<Integer, AlertNotification>();
     private final Context mContext;
 
     /**
@@ -43,31 +44,18 @@ public class NotificationHandler {
      * @param contentIntent  Intent used when the notification is clicked
      * @param clearIntent    Intent used when the notification is removed
      */
     public void add(int notificationID, String aImageUrl, String aAlertTitle,
                     String aAlertText, PendingIntent contentIntent) {
         // Remove the old notification with the same ID, if any
         remove(notificationID);
 
-        int icon = R.drawable.ic_status_logo;
-
         Uri imageUri = Uri.parse(aImageUrl);
-        final String scheme = imageUri.getScheme();
-        if ("drawable".equals(scheme)) {
-            String resource = imageUri.getSchemeSpecificPart();
-            resource = resource.substring(resource.lastIndexOf('/') + 1);
-            try {
-                final Class<R.drawable> drawableClass = R.drawable.class;
-                final Field f = drawableClass.getField(resource);
-                icon = f.getInt(null);
-            } catch (final Exception e) {} // just means the resource doesn't exist
-            imageUri = null;
-        }
-
+        int icon = BitmapUtils.getResource(imageUri, R.drawable.ic_status_logo);
         final AlertNotification notification = new AlertNotification(mContext, notificationID,
                 icon, aAlertTitle, aAlertText, System.currentTimeMillis(), imageUri);
 
         notification.setLatestEventInfo(mContext, aAlertTitle, aAlertText, contentIntent);
 
         notification.show();
         mAlertNotifications.put(notification.getId(), notification);
     }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/NotificationHelper.java
@@ -0,0 +1,113 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko;
+
+import org.mozilla.gecko.gfx.BitmapUtils;
+import org.mozilla.gecko.util.GeckoEventListener;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+
+public class NotificationHelper implements GeckoEventListener {
+    private static final String LOGTAG = "GeckoNotificationManager";
+	private Context mContext;
+
+    public NotificationHelper(Context context) {
+        mContext = context;
+        registerEventListener("Notification:Show");
+        registerEventListener("Notification:Hide");
+    }
+
+    private void registerEventListener(String event) {
+        GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
+    }
+
+    @Override
+    public void handleMessage(String event, JSONObject message) {
+        if (event.equals("Notification:Show")) {
+            showNotification(message);
+        } else if (event.equals("Notification:Hide")) {
+            hideNotification(message);
+        }
+    }
+
+    private void showNotification(JSONObject message) {
+        NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
+
+        // These attributes are required
+        String id;
+        try {
+            builder.setContentTitle(message.getString("title"));
+            builder.setContentText(message.getString("text"));
+            id = message.getString("id");
+        } catch (JSONException ex) {
+            Log.i(LOGTAG, "Error parsing", ex);
+            return;
+        }
+
+        Uri imageUri = Uri.parse(message.optString("smallicon"));
+        builder.setSmallIcon(BitmapUtils.getResource(imageUri, R.drawable.ic_status_logo));
+
+        JSONArray light = message.optJSONArray("light");
+        if (light != null && light.length() == 3) {
+            try {
+                builder.setLights(light.getInt(0),
+                                  light.getInt(1),
+                                  light.getInt(2));
+            } catch (JSONException ex) {
+                Log.i(LOGTAG, "Error parsing", ex);
+            }
+        }
+
+        boolean ongoing = message.optBoolean("ongoing");
+        builder.setOngoing(ongoing);
+
+        if (message.has("when")) {
+            int when = message.optInt("when");
+            builder.setWhen(when);
+        }
+
+        if (message.has("largeicon")) {
+            Bitmap b = BitmapUtils.getBitmapFromDataURI(message.optString("largeicon"));
+            builder.setLargeIcon(b);
+        }
+
+        // We currently don't support a callback when these are clicked.
+        // Instead we just open fennec.
+        Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CALLBACK);
+        String app = mContext.getClass().getName();
+        notificationIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, app);
+        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        PendingIntent pi = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);
+        builder.setContentIntent(pi);
+
+        NotificationManager manager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        manager.notify(id.hashCode(), builder.build());
+    }
+
+    private void hideNotification(JSONObject message) {
+        String id;
+        try {
+            id = message.getString("id");
+        } catch (JSONException ex) {
+            Log.i(LOGTAG, "Error parsing", ex);
+            return;
+        }
+        NotificationManager manager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        manager.cancel(id.hashCode());
+    }
+}
--- a/mobile/android/base/gfx/BitmapUtils.java
+++ b/mobile/android/base/gfx/BitmapUtils.java
@@ -9,18 +9,21 @@ import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.net.Uri;
 import android.util.Base64;
 import android.util.Log;
 
+import org.mozilla.gecko.R;
+
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.Field;
 import java.net.MalformedURLException;
 import java.net.URL;
 
 public final class BitmapUtils {
     private static final String LOGTAG = "GeckoBitmapUtils";
 
     private BitmapUtils() {}
 
@@ -200,10 +203,27 @@ public final class BitmapUtils {
         try {
             byte[] raw = Base64.decode(base64, Base64.DEFAULT);
             return BitmapUtils.decodeByteArray(raw);
         } catch (Exception e) {
             Log.e(LOGTAG, "exception decoding bitmap from data URI: " + dataURI, e);
         }
         return null;
     }
+
+    public static int getResource(Uri resourceUrl, int defaultIcon) {
+        int icon = defaultIcon;
+
+        final String scheme = resourceUrl.getScheme();
+        if ("drawable".equals(scheme)) {
+            String resource = resourceUrl.getSchemeSpecificPart();
+            resource = resource.substring(resource.lastIndexOf('/') + 1);
+            try {
+                final Class<R.drawable> drawableClass = R.drawable.class;
+                final Field f = drawableClass.getField(resource);
+                icon = f.getInt(null);
+            } catch (final Exception e) {} // just means the resource doesn't exist
+            resourceUrl = null;
+        }
+        return icon;
+    }
 }
 
new file mode 100644
index 0000000000000000000000000000000000000000..f0668dbe018e2305eed4d292b74fee1c51bb7b2b
GIT binary patch
literal 228
zc%17D@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBR(rZQhE&{od(Dum*+HN+kz4<P
z;w0rIn~lvs2tBiTrc%arO>j>`oZ<vOMHZQO#TzN+Y$dWEc>i2Ai8tLnuVY1*LKBCQ
zhqi{B=&RJbEB`Z|oxyv&>0{up+>bXHMHbxI@<z7VZ^g#Uqly|%`YKWmoWB$<GR|rP
zYO)e~vFthD+1TkZua-aF{-!5NTI2T1t@=KvxvTP9B<udPzgw2t;dNy#kJitV>KzJA
bTbkV#%+$1!dl|bC=xhd0S3j3^P6<r_^^jTt
new file mode 100644
index 0000000000000000000000000000000000000000..91e4d4fcef1c98359a1afdcf3d497b2e4a19ff1e
GIT binary patch
literal 397
zc$@)^0doF{P)<h;3K|Lk000e1NJLTq001Na001Ni1^@s6;Q*MJ00040Nkl<Zc-rlk
zF-ikL7)B*=O|vARcZi6UrLCnmuuh{zP=bON2tryxuoEwks8!0;>H)NhjUs3x!4S}_
zulS3PU2%$GH$mou$1vZ_&c0$9*o7oX<_e#OBRGOb7~6OVi?a|a!!=}LeYt3%J@_lT
zMGM_JgzkzK`g9153l?%G2nC@a6oi6M5SnwLsY7O(m(Yj2WJ&sGhtP}cjisOX3O&eS
zYtmmjgf3+7y7W)JLR~p*U-~+XAQS8BviCsNdcHypIcx|OS=)kFkT9p0Jrx+rdc#*J
zhBrB|lUlFB4jkK9O|5t2*>{M&gp4L=TGqR0fu?D_w@?ZCpy|QtZ~Lw9f~F58Um>Li
z14!o{&f&yH=9vtjmdC{y!I@l&u6)BOM0wxTn{WePkcstIuu=5C{W5H;iOpAv|F>5(
r)&(I&zNN{$2(@8k<Lrf$e<}0>5cWB15S=?&00000NkvXXu0mjfX9KVp
new file mode 100644
index 0000000000000000000000000000000000000000..45b565f5328965a5ec67477b4c65b3838d49a4dc
GIT binary patch
literal 242
zc%17D@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjJ3U<-Ln>~)y{gUE93as8@M24^
z$;(AIxYs*8V3HEL7JRpicWcN3|9*wJv%g3gtz`N*Q}Fq8WrlyRcw|%!c$|HNbzA1<
zsm#0YaR0mVK?fVbvn}}xUhis6P?*hqmvaf@bD5qNp{TvH*=}uX4a{(6_Pu;SW6Q$E
zUF#A>Ze4H)4K?7(%~+u0EzxRb5+K3~Rr|@NUXUlHcqx0M$%NFK9tHybF2<P(zL#gH
kRSB0>O{zFoU$Er>d!~f0Qg>dd5zrG1p00i_>zopr0FPf<u>b%7
new file mode 100644
index 0000000000000000000000000000000000000000..ace497e703c44683d9ddd9a9dc37c4a97a085b2a
GIT binary patch
literal 339
zc$@)K0j&OsP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv0003RNkl<Zc-qa)
zJ4ysm5QX6j9jckketHeM8-s>^zaL9+w&28~xCJ{K12r+4;}I&5q8e_`FfhRfm##zq
z4!~KNAE#;30xD@QN($8Gq(DSaqN4-jIBGLNc~_g00<at%7=}TEMQ!{8ecx-asLcfh
zx~|h;Q5*k2+qN1kYU3Yhnnr^~ZTtguU2CwYjenr3Dh(F3Srzyp&<#173Cb+DYXW_H
z2g<U1Ay7z;B7zc;lMU$AJ5UrwOrR|}3aF$#eaJf?CmT@i9mw;1L!ShCAZL*qWV`VW
zWLfs-jt&HRq>!#eNL#rbWb0N1#65ixY`n?OR|V2E{qL4?dLxKuO62B*e;`ScB@oVV
lL4i1qmpsvK;kFn!t6u^~j=<X-W7+@!002ovPDHLkV1m#}hY$b&
new file mode 100644
index 0000000000000000000000000000000000000000..e190d946be21065f06d16f72beef3c52a025a9af
GIT binary patch
literal 305
zc%17D@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjKRjI=Ln>~)y<+Wm*g>TA;Y#J{
z8#q+NrY?=Bi~jZ_@OID*KGPK5Ez1ffnzSp67&jXwne1|0wBX0G9Y=pYJ;rrl{jZuw
z7XQ*@j?dpxqGU10qn$-*R;HnH+-h?Jp2KO2)OT&qn{|$*-T3GJ1@C{Kbt+%tRKXm3
zJ=a0&LTSVCj<hI_#(8@g*M4;o2$&`-%MstWEmyp$VOPZhai*CLmrVqMwk*H8Yo<f&
zb%CPX84jVLU@CGNm<kjDQ}dn!sXI0smS2@TvFS~(!U8?Hms1_oCM<giG4uq>B$dvL
qW#vu_lr>LZ-T&g+KjwO!4~(Jm`_3qyvAG2d0|rl5KbLh*2~7YMA%Hyq
new file mode 100644
index 0000000000000000000000000000000000000000..85f0fbbd253b7808cf260c4971cfe80c57c174d7
GIT binary patch
literal 168
zc%17D@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj`JOJ0Ar-gYPPOK0aNu#-E~c+k
zq49p{gUU5mjc>dYyY^Uw?Z~NwYlqGr$X-_IYscsi785XGff{q?h4d<=7a#Q=I=Q;N
zT-xK-)OLoYoRNn`_9o{ij&nO6m^1wq$#tu~Fv<0lzj<U{V3XbI|ILS3**Of?Pwmc{
R8w|9X!PC{xWt~$(69DP=KKcLv
new file mode 100644
index 0000000000000000000000000000000000000000..6fba5ad7ea06475592d237dfe5b96508566b6993
GIT binary patch
literal 246
zc$@+D015wzP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv0002JNkl<Zc-rlh
zu?oU45I_Y-b(8*2z9IgNLqS3MJN6gqEQ-HiHyzZaljjW&gvLr@(?Q6=8`8Vvm2kO{
zhzyEv;aDk@3lw~dCdkE&Jf@%)H_q_{9gze{kOch%4F}A=yyTkPl=UM-aA1@1m_>&5
zF+{LpQ~6<<UR%?&q;?Y`$dnr!mrfqY3ALHG0Ic%lfGt-s0VDi5HTKsoS}1qv2y(V>
wo6fo4le}ZEtjR@r2jKSq-w7&Z7b+N(7h8`t(sIJYSO5S307*qoM6N<$f@iL0`~Uy|
new file mode 100644
index 0000000000000000000000000000000000000000..ac4382377f321bb1f237b4242dba2f8c277382ea
GIT binary patch
literal 214
zc%17D@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjb3I)gLn>~)onpw<Vj$oetUqmc
zyMWCVxeQxJmhV>A-pxzA9T73(+3#*mCy$2`%-ug8nle9}cqWCXVbL4QFG}1(6RTDW
zhAdI$yKS7!GHuUE<rQ0xD9f(j+o=3~lC4AC<j32urQcB6aH5-Y$HaLGB0HK{OeHy!
z82JQjQe6Vw6n3&K7ksh&#dK`}sZWhde)#*nUBvmTX6o&b^Qu4c4{|+ybG}MLZk{91
Op$wj`elF{r5}E)z(o}%}
new file mode 100644
index 0000000000000000000000000000000000000000..8952d9c7c3367b8b02f8cedeed8df9218e6705a5
GIT binary patch
literal 195
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`U7jwEAr-goUOvd_WGKLLA=61)
zczs!du%e=X&?GjN{#!Tq*ZknM@Z+;|Ejf5wzG8#U<h`sa|2MTWuie_c=8*h5dHyR=
z;R}yeEcvOxujr+@C8+AZ%IO=5&waFu-YGA20g=Z%KxB)9_R{a?H}%Nw`9AxCljHKA
p8vcrNio<WV#P4~s?ehK)Y<WH>rhTv8{0ZnP22WQ%mvv4FO#qU1PCNhr
new file mode 100644
index 0000000000000000000000000000000000000000..0b601b8ac3118c514e8ace851ad209faf8f70930
GIT binary patch
literal 268
zc$@(Y0rUQeP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0002fNkl<Zc-o!O
zI|{-;5XNy^3n3R!@CG?VjvyAfP9B?(cco|0!UrD4%EHP75-SC(@e52bMOLyd{9u?}
z{>@H^Xi!yEF4XV<SNBdED9f@1V%<Lhl8ggIQIKFB$n%^8^FWqmB$x-%G$p}2kR%BS
z;{tIUJ22;&B;GB+(FUR@YC-Yb0kMvE{b{rTrPLnoFasB^%!Mi3V5bd)VQ9k#j$nzs
z1qZ&fhXX7Kf;GJ12`4!7{DRe}U+{f@2?;b%Lk!FD-^e`AyY$4)IIxAyuQy+oLdXm*
S$dHZz0000<MNUMnLSTZ^x@z<Q
new file mode 100644
index 0000000000000000000000000000000000000000..82a8d314f2fd7f7b1073dadaefe1a9203f25793c
GIT binary patch
literal 236
zc$@+302BX-P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00029Nkl<Zc-qa=
zu?oU46a~<?yK28oT>S%oB57zTX`4|5K{x#m!5?wxQYh|TZ-y?m#0M@uI1nB;7cxWu
z{A6|+LRip&o`e~<WI@}udpgsS?t~BNf~IL6l&PLj*Y%cq!WXC60`GlK&=@cAyrK(1
zs;Yt(C+64GjXNpJ5?a7CFGW#63z+7`IR`CZnwLD!p~Z>$4RzyAthEC{gCh~o8C?lt
mjM>pZNO;jP@fURdv)~PVU%G!i`1?ix0000<MNUMnLSTaaM_*+C
new file mode 100644
index 0000000000000000000000000000000000000000..20efd1302cec09da93890363dfcafa976464a936
GIT binary patch
literal 262
zc%17D@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUtvz{)FAr-gY-Z12Aau9G!%+j`O
zJ9eZ(R$D(&``!`dm4W^Zvc0TV9`~OO+oc$ps9rhQMvH4-N>?nGaj`4|6gVgyhzWhQ
zDC+lrldRcX8^X5s+WT@jtjwC7*X3bc`s-WWe+!M%A{zqCQ};7G_hGcYs@ctWtCMl!
z)J|p{<pV}*daNBHB-b~bhnZ*L#lzYX_R3J?f~CFR@BJ4-BAE_(-Bo7zKmFIf-l|(a
tK70L4ULE3I8hY15e*g39N)TNScAAH%?c15*a$pjO>*?y}vd$@?2>{4KUhV(@
new file mode 100644
index 0000000000000000000000000000000000000000..1f0a8de9597d88d6c74c6d2e695c80a99bdd0803
GIT binary patch
literal 449
zc$@*j0Y3hTP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm0004qNkl<Zc-rln
zF-`(O6oz$(c5nerq%ek4NDaga4`8I!)<Y=Aa4R$cYP&U7asw!hqNUroiDvwZSC~H&
zmjy;<<^9Rm%$s56OE%2Tz=pN9(5Qd|15Q8;-i27&Oy~k-a0i@`S|wzGfz8e!RDmNq
zds-n1^m7RGLlk(;A@CBOK)!qyUBIe<3aEezsDKKnfC{L93aG%^3Vf|XAf-i=ss6UW
zk#d{rsSklEO>(frL;W7rr#=MkX_8~Aw-&foX;VG+A#g>LM2<c~o1LL!+%@&N^dV4p
zJg2+Vr!vRY;tu$resv!Ln_x^6^_FnC+oR7M1LNy!v;#O7Qp=5u<#c@R110e2xTHFt
zWASG)_Gd<*<a;Blfdp`F!G6|w>2m`(38?+mPTT;grAS)@+Mo)ygsQYfzl>}6-HF{2
ziCsJiE#A8TKTab%e@39T>X$hKPQk>^o{5YxA-#<na0t5K!BV8HD}7A(KeJM}t^z8s
r1_IA?0xO(AQx^UNn)+vt|0?herdik4VN~n-00000NkvXXu0mjfft1I)
new file mode 100644
index 0000000000000000000000000000000000000000..d2910ef53113999bad775ef858f2f19f01ca9da9
GIT binary patch
literal 343
zc%17D@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezr3(Fv@wlIEGZ*dOOpX>yUv!E35e<
z{u{{}O%;sda)+l*FUa2O?wYT3&%xEUv5DJU^i5Swx0B06iB~t=?ltFMKTu;JIcbZE
zQk-)m|B7bGj-$4PySB|+?x1p0IFrl7k;CkPnw4cXXUW09FT&XzEc{2;A7HpLB`RZ`
zq5)%*j^oc}-x|^HF_*XU@-~_ZEP4N2!c=<OqvZ{rolKG&&X@j;Ibg83p?2MC?-X&4
zPa5|Yimp!h_~-BG82uFv+;12@#U;cBGzxk&++fL@l>A0FS$qN$=N8}773v95$NU>j
zL>4fxYB(@*1vI#GhBG>|%fzq>o$tT=FFxat6W5Gsx0omY-*rX0>Ve9B|37Ej*E0AV
m6jQO+syj2k2B!1J3E8G5yG?mQ3yXmP#^CAd=d#Wzp$P!CmV<Zz
new file mode 100644
index 0000000000000000000000000000000000000000..cb233c298b1c422654ed62236df2d0b56f0c9c97
GIT binary patch
literal 349
zc$@)U0iyniP)<h;3K|Lk000e1NJLTq001Na001Ni1^@s6;Q*MJ0003bNkl<Zc-rmO
zJxT*%6ouhiWCK}*F2T-Dci<*;6~p}hB{KuM5<7*|3T{BRAfz)O0gtB`yv~r}YjVRB
zU-5Jo&RbpqFe^UD2l*f$G>f1h2&C(}8+xT3;fUTyT-cyNeGClAgazFb2!BCs+rAJC
z$pK4xBJ?z<X__6ukc^zs`{-s6gcAl`(Uve7v|g`aOnSGppSaPW)oKM}LarSRs_PoY
zgj_osR8<v>3AuJOs4Pnu6LRfnP*D^xCgj@DpghlEOvtsPL0Oi;n2>8ngVHpGG3njW
ze&R-hk|cpK`RAIpgb@vj<M@+cNJh@-V{|hJdOTT>TGI2upeTwi=$l~Rhh)Nn9tecN
v#~6m;C2i=7a6}st7dH4O-v{|1A2bWULd4VL%UBYj00000NkvXXu0mjfIW3Xb
new file mode 100644
index 0000000000000000000000000000000000000000..a2ee1464bd11cf8d0e7b26c233077192e8443978
GIT binary patch
literal 478
zc$@*=0U`d0P)<h;3K|Lk000e1NJLTq001Na001Ni1^@s6;Q*MJ0004{Nkl<Zc-rmQ
zKTASU7{~Fj%taLHJt)!C*y`vzXm+U~iYRCl_4?=fpCD+9-hft9qjUKJYO<veY6&R>
z^Vcsp!NY;>2@hN(9X=d*&Lj7`+&j1cV5)dtrqk(PHJi;K9nk}QsPmAPJbq^t<Q20>
zR|K7!FZvqPXf$>R9=81sx-|sd`5p9b2>S3fs9vwbWF9nCLA6>91~VR1tyW<$<3W{5
z1qS{GmCI!q%y^LNx-j_HL1v{=X-x1i2KOdmdhi=TSr+prL(sF>4U75EDyUd2-V>BH
zF~1;qI2XI?V*Y3w)FLQ*VjiObL8s<1vAZws+Ezh@LLo&^dK40O3F;GsJ|)CXh<f5a
zWfhdq=fm_uP+aXkLR)mK&WLvJ5|md8PZFd@?Gl6oaBlLwM-X=H-ZI5;oB-Vrgf_*0
z*^g6;Aly*EHb_1sN*#ixOJ`J2XP2PqP}KT6q|4=UK{}-|;SWCPgo1V-MswIEUDJr5
zQ}avOu=!F?61GA~f{>(D+dmtuWV2cP_?gXvw4f1zhEoxg$z&WFkmGZZ*}n|>0%Ku9
UJD0j=X#fBK07*qoM6N<$f*Cf_WdHyG
new file mode 100644
index 0000000000000000000000000000000000000000..890c2b5098b9fd38f29a2f1fe94760ec1e5a1016
GIT binary patch
literal 417
zc$@*D0bc%zP)<h;3K|Lk000e1NJLTq001Na001Ni1^@s6;Q*MJ0004KNkl<Zc-rmP
zyGjE=6oBCarVwn*LuhMfr-kSfBzXvZ2o1U4FQ89irw}X^1Z}mo@DfNB0}}A~7a=>v
zZYCx>$rf9D)tuq{<&3Cmq(n0)o6SnM+YLhoeW*nr8S^d;O9gG?0?!yi7w|qasMG1p
zYY%VGC4E6A77j`pF?Tq^mT{iKC)DBtshL6Tc6%P@Q1q|?3CY4iPf#2}oCdX8t$7Tg
z*o1`0Hz<t<jG!TU{C7~Z*-S$nk^l)@LZQ>3Mx${Lbx3?9abwPzL8BE1?cfFK8&t2?
z)iE&~W8gF61=VUbbxbBsF%FpVf~wW3Iwr8~iEvP*Qc=eQwmn`@xm;Gq1hzd94l0#O
z>X^W`#|tVJi|Uxbwr4FtXBb<93Wb6?CWaFX{N~J{{>p-YUAzVc<@5P#s6*l-iCc5d
z4BE$!qnKYvfCR3*AmhcD%jFKx!#6ZUJ&48h#TftX<DfVw4qCTAddbPI0coy-00000
LNkvXXu0mjf?ZU5t
--- a/mobile/android/chrome/content/WebrtcUI.js
+++ b/mobile/android/chrome/content/WebrtcUI.js
@@ -1,20 +1,83 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 var WebrtcUI = {
   observe: function(aSubject, aTopic, aData) {
-    if (aTopic == "getUserMedia:request") {
+    if (aTopic === "getUserMedia:request") {
       this.handleRequest(aSubject, aTopic, aData);
+    } else if (aTopic === "recording-device-events") {
+      switch (aData) {
+        case "shutdown":
+        case "starting":
+          this.notify();
+          break;
+      }
     }
   },
 
+  get notificationId() {
+    delete this.notificationId;
+    return this.notificationId = uuidgen.generateUUID().toString();
+  },
+
+  notify: function() {
+    let windows = MediaManagerService.activeMediaCaptureWindows;
+    let count = windows.Count();
+    let msg = {};
+    if (count == 0) {
+      msg = {
+        type: "Notification:Hide",
+        id: this.notificationId
+      }
+    } else {
+      msg = {
+        type: "Notification:Show",
+        id: this.notificationId,
+        title: Strings.brand.GetStringFromName("brandShortName"),
+        when: null, // hide the date row
+        light: [0xFF9500FF, 1000, 1000],
+        ongoing: true
+      };
+
+      let cameraActive = false;
+      let audioActive = false;
+      for (let i = 0; i < count; i++) {
+        let win = windows.GetElementAt(i);
+        let hasAudio = {};
+        let hasVideo = {};
+        MediaManagerService.mediaCaptureWindowState(win, hasVideo, hasAudio);
+        if (hasVideo.value) cameraActive = true;
+        if (hasAudio.value) audioActive = true;
+      }
+
+      if (cameraActive && audioActive) {
+        msg.text = Strings.browser.GetStringFromName("getUserMedia.sharingCameraAndMicrophone.message2");
+        msg.smallicon = "drawable:alert_mic_camera";
+      } else if (cameraActive) {
+        msg.text = Strings.browser.GetStringFromName("getUserMedia.sharingCamera.message2");
+        msg.smallicon = "drawable:alert_camera";
+      } else if (audioActive) {
+        msg.text = Strings.browser.GetStringFromName("getUserMedia.sharingMicrophone.message2");
+        msg.smallicon = "drawable:alert_mic";
+      } else {
+        // somethings wrong. lets throw
+        throw "Couldn't find any cameras or microphones being used"
+      }
+
+      if (count > 1)
+        msg.count = count;
+    }
+
+    sendMessageToJava(msg);
+  },
+
   handleRequest: function handleRequest(aSubject, aTopic, aData) {
     let { windowID: windowID, callID: callID } = JSON.parse(aData);
 
     let contentWindow = Services.wm.getOuterWindowWithId(windowID);
     let browser = BrowserApp.getBrowserForWindow(contentWindow);
     let params = aSubject.QueryInterface(Ci.nsIMediaStreamOptions);
 
     browser.ownerDocument.defaultView.navigator.mozGetUserMediaDevices(
@@ -47,17 +110,16 @@ var WebrtcUI = {
 
         let videoId = 0;
         if (inputs && inputs.videoDevice != undefined)
           videoId = inputs.videoDevice;
         if (videoDevices[videoId])
           allowedDevices.AppendElement(videoDevices[videoId]);
 
         Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID);
-        // TODO: Show tab-specific indicator for the active camera/mic access.
       }
     }];
   },
 
   // Get a list of string names for devices. Ensures that none of the strings are blank
   _getList: function(aDevices, aType) {
     let defaultCount = 0;
     return aDevices.map(function(device) {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -82,17 +82,17 @@ var LazyNotificationGetter = {
       Services.obs.removeObserver(o, o.notification);
     });
     this.observers = [];
   }
 };
 
 [
 #ifdef MOZ_WEBRTC
-  ["WebrtcUI", ["getUserMedia:request"], "chrome://browser/content/WebrtcUI.js"],
+  ["WebrtcUI", ["getUserMedia:request", "recording-device-events"], "chrome://browser/content/WebrtcUI.js"],
 #endif
   ["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"],
   ["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
   ["FindHelper", ["FindInPage:Find", "FindInPage:Prev", "FindInPage:Next", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
   ["PermissionsHelper", ["Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
   ["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
   ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
 ].forEach(function (aScript) {
--- a/mobile/android/locales/en-US/chrome/browser.properties
+++ b/mobile/android/locales/en-US/chrome/browser.properties
@@ -260,11 +260,11 @@ getUserMedia.shareRequest.label = Share
 getUserMedia.videoDevice.default = Camera %S
 getUserMedia.videoDevice.front = Front facing
 getUserMedia.videoDevice.back = Back facing
 getUserMedia.videoDevice.none = No Video
 getUserMedia.videoDevice.prompt = Camera to use
 getUserMedia.audioDevice.default = Microphone %S
 getUserMedia.audioDevice.none = No Audio
 getUserMedia.audioDevice.prompt = Microphone to use
-getUserMedia.sharingCamera.message = You are currently sharing your camera with %S.
-getUserMedia.sharingMicrophone.message = You are currently sharing your microphone with %S.
-getUserMedia.sharingCameraAndMicrophone.message = You are currently sharing your camera and microphone with %S.
+getUserMedia.sharingCamera.message2 = Camera is on
+getUserMedia.sharingMicrophone.message2 = Microphone is on
+getUserMedia.sharingCameraAndMicrophone.message2 = Camera and microphone are on