From 8c92797a167ea3654f0daf6e912a849ac35efe06 Mon Sep 17 00:00:00 2001 From: Mark Finkle Date: Mon, 20 Jan 2014 17:26:30 -0500 Subject: [PATCH] Bug 953381 - Basic media casting control bar r=wesj --- CLOBBER | 2 +- mobile/android/base/BrowserApp.java | 7 ++ mobile/android/base/MediaCastingBar.java | 118 ++++++++++++++++++ .../base/locales/en-US/android_strings.dtd | 7 ++ mobile/android/base/moz.build | 1 + .../drawable-hdpi/media_bar_pause.png | Bin 0 -> 362 bytes .../drawable-hdpi/media_bar_play.png | Bin 0 -> 670 bytes .../drawable-hdpi/media_bar_stop.png | Bin 0 -> 763 bytes .../drawable-mdpi/media_bar_pause.png | Bin 0 -> 252 bytes .../drawable-mdpi/media_bar_play.png | Bin 0 -> 463 bytes .../drawable-mdpi/media_bar_stop.png | Bin 0 -> 497 bytes .../drawable-xhdpi/media_bar_pause.png | Bin 0 -> 389 bytes .../drawable-xhdpi/media_bar_play.png | Bin 0 -> 853 bytes .../drawable-xhdpi/media_bar_stop.png | Bin 0 -> 1261 bytes .../base/resources/layout/gecko_app.xml | 7 ++ .../base/resources/layout/media_casting.xml | 41 ++++++ mobile/android/base/strings.xml.in | 5 + mobile/android/chrome/content/CastingApps.js | 30 +++++ 18 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 mobile/android/base/MediaCastingBar.java create mode 100644 mobile/android/base/resources/drawable-hdpi/media_bar_pause.png create mode 100644 mobile/android/base/resources/drawable-hdpi/media_bar_play.png create mode 100644 mobile/android/base/resources/drawable-hdpi/media_bar_stop.png create mode 100644 mobile/android/base/resources/drawable-mdpi/media_bar_pause.png create mode 100644 mobile/android/base/resources/drawable-mdpi/media_bar_play.png create mode 100644 mobile/android/base/resources/drawable-mdpi/media_bar_stop.png create mode 100644 mobile/android/base/resources/drawable-xhdpi/media_bar_pause.png create mode 100644 mobile/android/base/resources/drawable-xhdpi/media_bar_play.png create mode 100644 mobile/android/base/resources/drawable-xhdpi/media_bar_stop.png create mode 100644 mobile/android/base/resources/layout/media_casting.xml diff --git a/CLOBBER b/CLOBBER index 81ae8acf1b75..85664dcf8fc9 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Bug 917896 requires a clobber due to bug 961339. +Bug 953381 requires a clobber due to bug 961339. diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index b99e92165339..fa3710b88031 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -156,6 +156,7 @@ abstract public class BrowserApp extends GeckoApp }; private FindInPageBar mFindInPageBar; + private MediaCastingBar mMediaCastingBar; private boolean mAccessibilityEnabled = false; @@ -530,6 +531,7 @@ abstract public class BrowserApp extends GeckoApp } mFindInPageBar = (FindInPageBar) findViewById(R.id.find_in_page); + mMediaCastingBar = (MediaCastingBar) findViewById(R.id.media_casting); registerEventListener("CharEncoding:Data"); registerEventListener("CharEncoding:State"); @@ -830,6 +832,11 @@ abstract public class BrowserApp extends GeckoApp mFindInPageBar = null; } + if (mMediaCastingBar != null) { + mMediaCastingBar.onDestroy(); + mMediaCastingBar = null; + } + if (mSharedPreferencesHelper != null) { mSharedPreferencesHelper.uninit(); mSharedPreferencesHelper = null; diff --git a/mobile/android/base/MediaCastingBar.java b/mobile/android/base/MediaCastingBar.java new file mode 100644 index 000000000000..98ae045b01f4 --- /dev/null +++ b/mobile/android/base/MediaCastingBar.java @@ -0,0 +1,118 @@ +/* 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.util.GeckoEventListener; +import org.mozilla.gecko.util.ThreadUtils; + +import org.json.JSONObject; + +import android.content.Context; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageButton; +import android.widget.RelativeLayout; +import android.widget.TextView; + +public class MediaCastingBar extends RelativeLayout implements View.OnClickListener, GeckoEventListener { + private static final String LOGTAG = "MediaCastingBar"; + + private TextView mCastingTo; + private ImageButton mMediaPlay; + private ImageButton mMediaPause; + private ImageButton mMediaStop; + + private boolean mInflated = false; + + public MediaCastingBar(Context context, AttributeSet attrs) { + super(context, attrs); + + GeckoAppShell.getEventDispatcher().registerEventListener("Casting:Started", this); + GeckoAppShell.getEventDispatcher().registerEventListener("Casting:Stopped", this); + } + + public void inflateContent() { + LayoutInflater inflater = LayoutInflater.from(getContext()); + View content = inflater.inflate(R.layout.media_casting, this); + + mMediaPlay = (ImageButton) content.findViewById(R.id.media_play); + mMediaPlay.setOnClickListener(this); + mMediaPause = (ImageButton) content.findViewById(R.id.media_pause); + mMediaPause.setOnClickListener(this); + mMediaStop = (ImageButton) content.findViewById(R.id.media_stop); + mMediaStop.setOnClickListener(this); + + mCastingTo = (TextView) content.findViewById(R.id.media_casting_to); + + // Capture clicks on the rest of the view to prevent them from + // leaking into other views positioned below. + content.setOnClickListener(this); + + mInflated = true; + } + + public void show() { + if (!mInflated) + inflateContent(); + + setVisibility(VISIBLE); + } + + public void hide() { + setVisibility(GONE); + } + + public void onDestroy() { + GeckoAppShell.getEventDispatcher().unregisterEventListener("Casting:Started", this); + GeckoAppShell.getEventDispatcher().unregisterEventListener("Casting:Stopped", this); + } + + // View.OnClickListener implementation + @Override + public void onClick(View v) { + final int viewId = v.getId(); + + if (viewId == R.id.media_play) { + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Casting:Play", "")); + mMediaPlay.setVisibility(GONE); + mMediaPause.setVisibility(VISIBLE); + } else if (viewId == R.id.media_pause) { + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Casting:Pause", "")); + mMediaPause.setVisibility(GONE); + mMediaPlay.setVisibility(VISIBLE); + } else if (viewId == R.id.media_stop) { + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Casting:Stop", "")); + } + } + + // GeckoEventListener implementation + @Override + public void handleMessage(final String event, final JSONObject message) { + final String device = message.optString("device"); + + ThreadUtils.postToUiThread(new Runnable() { + @Override + public void run() { + if (event.equals("Casting:Started")) { + show(); + if (!TextUtils.isEmpty(device)) { + mCastingTo.setText(device); + } else { + // Should not happen + mCastingTo.setText(""); + Log.d(LOGTAG, "Device name is empty."); + } + mMediaPlay.setVisibility(GONE); + mMediaPause.setVisibility(VISIBLE); + } else if (event.equals("Casting:Stopped")) { + hide(); + } + } + }); + } +} diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index 2772224df0f9..b363d013d8dc 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -219,6 +219,13 @@ size. --> + + + + + + diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index 04bae7c6fcf3..3ca252197e23 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -248,6 +248,7 @@ gbjar.sources += [ 'LightweightTheme.java', 'LightweightThemeDrawable.java', 'LocaleManager.java', + 'MediaCastingBar.java', 'MemoryMonitor.java', 'menu/GeckoMenu.java', 'menu/GeckoMenuInflater.java', diff --git a/mobile/android/base/resources/drawable-hdpi/media_bar_pause.png b/mobile/android/base/resources/drawable-hdpi/media_bar_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..5f635fc10aba60c7360e69170c69507a1f4df38c GIT binary patch literal 362 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmSQK*5Dp-y;YjHK@;M7UB8wRq zL=J;6qiBJz6HrjH#5JNMI6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWIa~o-U3d z8I5meI&vK{5OGmYTB-g~>XEHY%id1w{0F8k{-H!Sq>y#pus7`&)UF z9E5Z?2bzD-7Yj&cG(F3m6MjX8QHJX$$Ad}F^2->`WuE)UP_X`0Tguyti9IUI&nTQs zT$tw(xlV6cdidAP&v;8R`DWTMy%4|ld&*S1+v_IiUixF7z-S6K4G*#TS&3DW-OO`$ z&*y!pHfLZ6K9#ggkD=na*=Dx~ED7-^+!?O7ewBWv{O!`b^S9EP^miSNaOh>dVxs?n ZQ7LXG$Haem)<7>ac)I$ztaD0e0ssbzf=~be literal 0 HcmV?d00001 diff --git a/mobile/android/base/resources/drawable-hdpi/media_bar_play.png b/mobile/android/base/resources/drawable-hdpi/media_bar_play.png new file mode 100644 index 0000000000000000000000000000000000000000..ac69ea59a43370a3d777570c7e8d5a882a32cf13 GIT binary patch literal 670 zcmV;P0%84$P)WIW$s*yGZ)<%&79|ZzV~}4^R8QWbDrn(>3Qxs z&pEf%?RG6>Mw%8-l|=(hvuL1cA_ku00+trm5~3B@Mtch;un(__D#|2Mfu4c2c#6?- za`LJ28)#z%ZsI(a6j78(q=h_UpHV!)hAKgtG%$iS zc!c9sg5*;28KW&7ikEOr8V<8)+5msfheMKyEQZ0r-!Wh0E^K6m@1ryQIf*N|iz5a~ zR-l9T(vCC{3D9&4jbojlc$zWLnT~N5JMpPXutB%bB)&=aggX`D{AQ2_y3+k%58ec0 z%wQ1HXwUFvY*tV_^;cjLQ#gUEfn0M^r-3QF!)|G4tA#pSXbkJsD4z1$Xm5)=gI)L> z5tT#97V1cslLNRLR+LF816@4EUJLALvG@HVwivK@s#TyPz0w`S^>AT0EM$o z9CP3=uqtc*0mhyhp2a=@ziZ~BZW7ENm9kx^2D(lK-gk1RhAge1`xhDv}#-YwhkadET$aJCN;(G&H+SvJXTeW+r0yb z5R<7|kucq2DsjV`8ve$E&vL`3zpYV3dEA|;@BEML3p+G8hIxCM=TN{=4}8JgiA$=`D#3R z<90>3M9p#mrU~X(G{>Shb|b_f2cS>5SX(ajmN@jp?~>3^=;Z*2(AubX<2ISM#GoaH zW0!97Qax)5jo93ki-*n=f7{|XcIQkjM(ctiwEODax!BV*$6jSL#d7S*wcc8cW6Zis z7Y~^!?ixlto?|!8)KW|Z5aGFe!WeS~h#1rN7+Q(-Q9#mn;+~}Ul73~s&xh{|A0_>e t^yr?P2=Hq3X1|fW8XW+-)&Zbv{R1#$Nns?;kY)e?002ovPDHLkV1gB0Q;7fo literal 0 HcmV?d00001 diff --git a/mobile/android/base/resources/drawable-mdpi/media_bar_pause.png b/mobile/android/base/resources/drawable-mdpi/media_bar_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..81712cabed27c3aba37a0789a41f0196af80459d GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt+|FE{-7) zt#7Xzay1z6v_9PbYRS5}ReksVn_N10)N#&~T^3Uoy;vafkkv@VW>)^%PQ##0s}@~n z{lTrk@Y?gY%42<_hrA7}uhxhkP?aoy^Jz{k^M_1Mg$4#D4lr@S=ENC?`ER2VJf3mB mXwW^~Ti!2SdzM9m_XgvIUy_Znsh%MJF?hQAxvXBie0SW?WX~1==y@V#T0Jx0!Y#W-5A6v=86DljPJuXHpG?5q$CkE>jhVs z#(i3VEZeXLLpaAo5dhGMe(YgA&3aiPXjTPhn8jl#K%OUIFGg{R@ge|-D>Z;4tfACu zLY@&ctAb<9e>bSzm(fM50U=+O^ub93~CooM2v0`B;fAv8Ps(64nJ zNUsNB#yF{oGC)De64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq!<_&nLJ$_ zLn`LHon^?^I4>+V0=D)I5h5WIr3jw=V)YoM7gR#A8d7-rAlleDKTk}z3zOcu sKl>5#ZT&|vpv5o?3>kDCHveMqmr>R5lze6;0Sqz*Pgg&ebxsLQ095geRsaA1 literal 0 HcmV?d00001 diff --git a/mobile/android/base/resources/drawable-xhdpi/media_bar_play.png b/mobile/android/base/resources/drawable-xhdpi/media_bar_play.png new file mode 100644 index 0000000000000000000000000000000000000000..2ce52f803941cc582a296542acddb3c94c9ccc7b GIT binary patch literal 853 zcmV-b1FHOqP)_*~G?7_cPi$#-j0(cdPuQ7=o0rf)3 zZ9p&UrE7-6L3Kljh`=m9T;g|r#}9Z|wRlzPoPb_-Nxu@?0_sW#i7?<1)>ai+CO7eA6@j)zU@;R_1=?!B3mg(~ z*}SrPFSQ%+pY(k3a)el04Cp>wy&`RVQ&GZgiNF9(V7tJ@(~b#vf_*Z*b8NZ+xA5J< z-z+pa0^`^s|D{P2a7nu3t8MW#!+;KsVJ99` zZ)a&lL}0$ccOPRfE(G+kEK2SHH}EZP2lTLPN=0A{TNPeB^&2pUpD}``0ktbrX9Vs^ z2dAzE)C?o{1a$v*5npN8;;APBN2K!r#ScTgiJTFbmQI2E7EmJ_xeS<;?&9tQ=w~HI z1kOlDUmCx7Vhxy;9#W1Cn6F=#I^CU%t8<) zD#1WdaFdOHM79nDg9*{lF(E`EAxqzjVVI0DCcbsZN|XpjP~64&T%0cK?&-&^s?$?Q z`h$XL=H7G8_uZ;n=bkz>N!-mCD;+>-j07khKxvEwC>=m)j07khKxvEwC>=m)cnL58 zG=NLMvXjkTI0766W?ZaE${mxER`nllO4{RKx7%20@pE0$bgs?ZfbNsDrtR32wB$&< zCh1ty_Ew9ZYm(}v1lTQUy$kt(pCm0hkw4yre85#nQ#&odl%$P5uL?7}2 z8?pzoBLdWAs#nE#lI9cqj7d6GfqcNaY)KAPfN9yfi^VsR<|4_TjvybfCVMFlMSvOE zn>Q6-Nt&(TXIxUFc~1=afK^Eo75qdCa5N#9P7K<*#wDF?UYkNbpwY)i+lWboehsNZ zM%u9#_zTVh~zvSY5Uf-OVR}=+czaGt0JdZ0Z{jZ zBn4sXx<}H*Oj`k)ip)Z5QuK_j0dE4Yl0Q|i0Jnhs88!kofqlTwF*l_M07m*P;4t|j zc;H8j{4cRLrhW|ycm;SHBcJnP%srU+9(bGt@{m3Q0>&^A4pL?&d<#5*Eu$pl)&sVH zL%X2U~*> zGR*_PJ2=FjYhWJu1b8SnkO%}3Ucx@avN+`2ya0R(JeXl4HAe^{90X31vDKyuu!xa= zAk}dbIXOZQ;Sg4wNwSq9z!LBoaDR%!4qV2vyi^1c4r67ycoAX>h|PlIrc(j)qO2gg zTh-1P`ymu)k14Xq8*}lptmNzw=ml9RJlLL6+<9X^+bbH90GFHsJg;{0Vbqm&?%2)8 zkuLVLPlo=CUSBOM4O^e*fETbY#!lSEK6h_q_zCZTh3b)2p8*qUZ-t3{Dh=7$-ylD; zvV+uAT$df>4&`KMQr%l(dLfWMEj#kA>Ss=Nq#lcFitHPT6#!WiLci9SPV%QB$%ip7 zYp$q@t4in>N)Z4J-ns($6S6j!R6h%{Hk(1%P-34@YGA1M))mMf&m7 zACxNq+P-zQCL|u@!&p-H1Z+(SeL + + + + + + + + + + + + + + + + + diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index a6dc720325ab..08de2ef70e74 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -83,6 +83,11 @@ &find_next; &find_close; + &media_casting_to; + &media_play; + &media_pause; + &media_stop; + &settings; &settings_title; &pref_category_advanced; diff --git a/mobile/android/chrome/content/CastingApps.js b/mobile/android/chrome/content/CastingApps.js index 043dda6b3848..a6ed31961c75 100644 --- a/mobile/android/chrome/content/CastingApps.js +++ b/mobile/android/chrome/content/CastingApps.js @@ -19,9 +19,17 @@ var CastingApps = { this.filterCast, this.openExternal.bind(this) ); + + Services.obs.addObserver(this, "Casting:Play", false); + Services.obs.addObserver(this, "Casting:Pause", false); + Services.obs.addObserver(this, "Casting:Stop", false); }, uninit: function ca_uninit() { + Services.obs.removeObserver(this, "Casting:Play"); + Services.obs.removeObserver(this, "Casting:Pause"); + Services.obs.removeObserver(this, "Casting:Stop"); + NativeWindow.contextmenus.remove(this._castMenuId); }, @@ -29,6 +37,26 @@ var CastingApps = { return Services.prefs.getBoolPref("browser.casting.enabled"); }, + observe: function (aSubject, aTopic, aData) { + switch (aTopic) { + case "Casting:Play": + if (this.session && this.session.remoteMedia.status == "paused") { + this.session.remoteMedia.play(); + } + break; + case "Casting:Pause": + if (this.session && this.session.remoteMedia.status == "started") { + this.session.remoteMedia.pause(); + } + break; + case "Casting:Stop": + if (this.session) { + this.closeExternal(); + } + break; + } + }, + makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) { return Services.io.newURI(aURL, aOriginCharset, aBaseURI); }, @@ -192,9 +220,11 @@ var CastingApps = { } aRemoteMedia.load(this.session.data); + sendMessageToJava({ type: "Casting:Started", device: this.session.service.friendlyName }); }, onRemoteMediaStop: function(aRemoteMedia) { + sendMessageToJava({ type: "Casting:Stopped" }); }, onRemoteMediaStatus: function(aRemoteMedia) {