diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 90e3a50eb53d..0b335185f92f 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -517,8 +517,7 @@ abstract public class GeckoApp loadUrlInTab("about:addons"); return true; case R.id.downloads: - intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS); - startActivity(intent); + loadUrlInTab("about:downloads"); return true; case R.id.char_encoding: GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("CharEncoding:Get", null)); diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 3067577e442c..c827a807899c 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -178,7 +178,6 @@ FENNEC_PP_JAVA_FILES = \ FENNEC_PP_XML_FILES = \ res/layout/abouthome_content.xml \ res/menu/gecko_menu.xml \ - res/menu-v11/gecko_menu.xml \ $(NULL) diff --git a/mobile/android/base/resources/menu-v11/gecko_menu.xml.in b/mobile/android/base/resources/menu-v11/gecko_menu.xml.in deleted file mode 100644 index da6e285ecc81..000000000000 --- a/mobile/android/base/resources/menu-v11/gecko_menu.xml.in +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - -#ifdef MOZ_PROFILING - -#endif - - - diff --git a/mobile/android/base/resources/menu/gecko_menu.xml.in b/mobile/android/base/resources/menu/gecko_menu.xml.in index 6efa3b697a58..da6e285ecc81 100644 --- a/mobile/android/base/resources/menu/gecko_menu.xml.in +++ b/mobile/android/base/resources/menu/gecko_menu.xml.in @@ -28,8 +28,7 @@ android:title="@string/addons"/> + android:title="@string/downloads"/> " + + "" + + "
" + + "
" + + // This is a hack so that we can crop this label in its center + "" + + "
{date}
" + + "
" + + "
{size}
" + + "
{domain}
" + + "
" + +""; + +XPCOMUtils.defineLazyGetter(window, "gChromeWin", function () + window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow) + .QueryInterface(Ci.nsIDOMChromeWindow)); + +let Downloads = { + init: function () { + this._list = document.getElementById("downloads-list"); + this._list.addEventListener("click", function (event) { + let target = event.target; + while (target && target.nodeName != "li") { + target = target.parentNode; + } + + Downloads.openDownload(target); + }, false); + + let contextmenus = gChromeWin.NativeWindow.contextmenus; + Downloads.openMenuItem = contextmenus.add(gStrings.GetStringFromName("downloadAction.open"), + contextmenus.SelectorContext("li[mozDownload]"), + function (aTarget) { + Downloads.openDownload(aTarget); + } + ); + Downloads.removeMenuItem = contextmenus.add(gStrings.GetStringFromName("downloadAction.remove"), + contextmenus.SelectorContext("li[mozDownload]"), + function (aTarget) { + Downloads.removeDownload(aTarget); + } + ); + + Services.obs.addObserver(this, "dl-failed", false); + Services.obs.addObserver(this, "dl-done", false); + Services.obs.addObserver(this, "dl-cancel", false); + + this.getDownloads(); + }, + + uninit: function () { + let contextmenus = gChromeWin.NativeWindow.contextmenus; + contextmenus.remove(this.openMenuItem); + contextmenus.remove(this.removeMenuItem); + + Services.obs.removeObserver(this, "dl-failed"); + Services.obs.removeObserver(this, "dl-done"); + Services.obs.removeObserver(this, "dl-cancel"); + }, + + observe: function (aSubject, aTopic, aData) { + let download = aSubject.QueryInterface(Ci.nsIDownload); + switch (aTopic) { + case "dl-failed": + case "dl-cancel": + case "dl-done": + if (!this._getElementForDownload(download.id)) { + let item = this._createItem(downloadTemplate, { + id: download.id, + target: download.displayName, + icon: "moz-icon://" + download.displayName + "?size=64", + date: DownloadUtils.getReadableDates(new Date())[0], + domain: DownloadUtils.getURIHost(download.source.spec)[0], + size: DownloadUtils.convertByteUnits(download.size).join(""), + }); + this._list.insertAdjacentHTML("afterbegin", item); + break; + } + } + }, + + _initStatement: function dv__initStatement() { + if (this._stmt) + this._stmt.finalize(); + + this._stmt = this._dlmgr.DBConnection.createStatement( + "SELECT id, name, source, state, startTime, endTime, referrer, " + + "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " + + "FROM moz_downloads " + + "WHERE NOT isActive " + + "ORDER BY endTime DESC"); + }, + + _createItem: function (aTemplate, aValues) { + let t = aTemplate; + for (let i in aValues) { + let regEx = new RegExp("{" + i + "}", "g"); + t = t.replace(regEx, aValues[i]); + } + return t; + }, + + _stepDownloads: function dv__stepDownloads(aNumItems) { + try { + if (!this._stmt.executeStep()) { + this._stmt.finalize(); + this._stmt = null; + return; + } + + // Try to get the attribute values from the statement + let attrs = { + id: this._stmt.row.id, + target: this._stmt.row.name, + icon: "moz-icon://" + this._stmt.row.name + "?size=64", + date: DownloadUtils.getReadableDates(new Date(this._stmt.row.endTime/1000))[0], + domain: DownloadUtils.getURIHost(this._stmt.row.source)[0], + size: DownloadUtils.convertByteUnits(this._stmt.row.maxBytes).join(""), + }; + + let item = this._createItem(downloadTemplate, attrs); + this._list.insertAdjacentHTML("beforeend", item); + } catch (e) { + // Something went wrong when stepping or getting values, so clear and quit + console.log("Error: " + e); + this._stmt.reset(); + return; + } + + // Add another item to the list if we should; otherwise, let the UI update + // and continue later + if (aNumItems > 1) { + this._stepDownloads(aNumItems - 1); + } else { + // Use a shorter delay for earlier downloads to display them faster + let delay = Math.min(this._list.itemCount * 10, 300); + let self = this; + this._timeoutID = setTimeout(function () { self._stepDownloads(5); }, delay); + } + }, + + getDownloads: function () { + this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); + + this._initStatement(); + + clearTimeout(this._timeoutID); + + this._stmt.reset(); + this._stmt.bindInt32Parameter(0, Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED); + this._stmt.bindInt32Parameter(1, Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING); + this._stmt.bindInt32Parameter(2, Ci.nsIDownloadManager.DOWNLOAD_PAUSED); + this._stmt.bindInt32Parameter(3, Ci.nsIDownloadManager.DOWNLOAD_QUEUED); + this._stmt.bindInt32Parameter(4, Ci.nsIDownloadManager.DOWNLOAD_SCANNING); + + // Take a quick break before we actually start building the list + let self = this; + this._timeoutID = setTimeout(function () { + self._stepDownloads(1); + }, 0); + }, + + _getElementForDownload: function (aKey) { + return this._list.querySelector("li[downloadID='" + aKey + "']"); + }, + + _getDownloadForElement: function (aElement) { + let id = parseInt(aElement.getAttribute("downloadID")); + return this._dlmgr.getDownload(id); + }, + + openDownload: function (aItem) { + let f = null; + try { + let download = this._getDownloadForElement(aItem); + f = download.targetFile; + } catch(ex) { } + + try { + if (f) f.launch(); + } catch (ex) { } + }, + + removeDownload: function (aItem) { + let f = null; + try { + let download = this._getDownloadForElement(aItem); + f = download.targetFile; + } catch(ex) { + // even if there is no file, pretend that there is so that we can remove + // it from the list + f = { leafName: "" }; + } + + this._dlmgr.removeDownload(aItem.getAttribute("downloadID")); + + this._list.removeChild(aItem); + + try { + if (f) f.remove(false); + } catch(ex) { } + }, +} diff --git a/mobile/android/chrome/content/aboutDownloads.xhtml b/mobile/android/chrome/content/aboutDownloads.xhtml new file mode 100644 index 000000000000..106bb89bee13 --- /dev/null +++ b/mobile/android/chrome/content/aboutDownloads.xhtml @@ -0,0 +1,34 @@ + + + +%brandDTD; + +%globalDTD; + +%downloadsDTD; +]> + + + + + + &aboutDownloads.title; + + + + + + +
+
&aboutDownloads.header;
+
+ + &aboutDownloads.empty; +