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 146178 d038d584102c8535594fa6da75dfad2bb81484c5
parent 146177 4e0f0d13afd3c731de78aa74625fa6a38a3d9d98
child 146179 14b4165dc0e28e10ca75988d06a16bfcf2c91c67
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs874401
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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