Bug 1204557 - Stop using base64 for native app icons in the application registry r=jchen
authorFabrice Desré <fabrice@mozilla.com>
Thu, 24 Sep 2015 11:53:05 -0700
changeset 264301 06c46075db465d7b0899a663ade1f5147d79fc27
parent 264282 e014082c421e633a87e1185dfb6e062af508eb2c
child 264302 737517ce8115b2b5ecea745ff52918a7192dd092
child 264375 3a1c802e1b263cc35327acbfb27fb38acb531905
push id65590
push userkwierso@gmail.com
push dateFri, 25 Sep 2015 00:14:23 +0000
treeherdermozilla-inbound@0ab67cace54f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjchen
bugs1204557
milestone44.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 1204557 - Stop using base64 for native app icons in the application registry r=jchen
dom/apps/AndroidUtils.jsm
mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Apps.java
mobile/android/base/GeckoAppShell.java
widget/android/AndroidBridge.cpp
--- a/dom/apps/AndroidUtils.jsm
+++ b/dom/apps/AndroidUtils.jsm
@@ -52,17 +52,18 @@ this.AndroidUtils = {
     return [app.android_packagename, app.android_classname];
   },
 
   buildAndroidAppData: function(aApp) {
     // Use the package and class name to get a unique origin.
     // We put the version with the normal case as part of the manifest url.
     let [origin, manifestURL] =
       this.getOriginAndManifestURL(aApp.packagename);
-    // TODO: Bug 1204557 to improve the icons support.
+    // We choose 96 as an arbitrary size since we can only get one icon
+    // from Android.
     let manifest = {
       name: aApp.name,
       icons: { "96": aApp.icon }
     }
     debug("Origin is " + origin);
     let appData = {
       app: {
         installOrigin: origin,
--- a/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Apps.java
+++ b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Apps.java
@@ -71,27 +71,17 @@ class Apps extends BroadcastReceiver
             obj.put("classname", info.name);
 
             final ApplicationInfo appInfo = info.applicationInfo;
             // Pre-installed apps can't be uninstalled.
             final boolean removable =
                 (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0;
 
             obj.put("removable", removable);
-
-            // For now, create a data: url for the icon, since we need additional
-            // android:// protocol support for icons. Once it's there we'll do
-            // something like: obj.put("icon", "android:icon/" + info.packageName);
-            Drawable d = pm.getApplicationIcon(info.packageName);
-            Bitmap bitmap = ((BitmapDrawable)d).getBitmap();
-            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-            bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
-            byte[] byteArray = byteArrayOutputStream.toByteArray();
-            String encoded = Base64.encodeToString(byteArray, Base64.DEFAULT);
-            obj.put("icon", "data:image/png;base64," + encoded);
+            obj.put("icon", "android://icon/" + appInfo.packageName);
         } catch(Exception ex) {
             Log.wtf(LOGTAG, "Error building ActivityInfo JSON", ex);
         }
         return obj;
     }
 
     public void handleMessage(String event, JSONObject message) {
         Log.w(LOGTAG, "Received " + event);
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -8,16 +8,19 @@ package org.mozilla.gecko;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.net.MalformedURLException;
 import java.net.Proxy;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLConnection;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -2587,26 +2590,92 @@ public class GeckoAppShell
         toast.show();
     }
 
     @WrapForJNI(allowMultithread = true)
     static InputStream createInputStream(URLConnection connection) throws IOException {
         return connection.getInputStream();
     }
 
+    private static class BitmapConnection extends URLConnection {
+        private Bitmap bitmap;
+
+        BitmapConnection(Bitmap b) throws MalformedURLException, IOException {
+            super(null);
+            bitmap = b;
+        }
+
+        @Override
+        public void connect() {}
+
+        @Override
+        public InputStream getInputStream() throws IOException {
+            return new BitmapInputStream();
+        }
+
+        @Override
+        public String getContentType() {
+            return "image/png";
+        }
+
+        private final class BitmapInputStream extends PipedInputStream {
+            private boolean mHaveConnected = false;
+
+            @Override
+            public synchronized int read(byte[] buffer, int byteOffset, int byteCount)
+                                    throws IOException {
+                if (mHaveConnected) {
+                    return super.read(buffer, byteOffset, byteCount);
+                }
+
+                final PipedOutputStream output = new PipedOutputStream();
+                connect(output);
+                ThreadUtils.postToBackgroundThread(
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            try {
+                                bitmap.compress(Bitmap.CompressFormat.PNG, 100, output);
+                                output.close();
+                            } catch (IOException ioe) {}
+                        }
+                    });
+                mHaveConnected = true;
+                return super.read(buffer, byteOffset, byteCount);
+            }
+        }
+    }
+
     @WrapForJNI(allowMultithread = true, narrowChars = true)
     static URLConnection getConnection(String url) {
         try {
             String spec;
             if (url.startsWith("android://")) {
                 spec = url.substring(10);
             } else {
                 spec = url.substring(8);
             }
 
+            // Check if we are loading a package icon.
+            try {
+                if (spec.startsWith("icon/")) {
+                    String[] splits = spec.split("/");
+                    if (splits.length != 2) {
+                        return null;
+                    }
+                    final String pkg = splits[1];
+                    final PackageManager pm = getContext().getPackageManager();
+                    final Drawable d = pm.getApplicationIcon(pkg);
+                    final Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(d);
+                    return new BitmapConnection(bitmap);
+                }
+            } catch(Exception ex) {
+                Log.e(LOGTAG, "error", ex);
+            }
+
             // if the colon got stripped, put it back
             int colon = spec.indexOf(':');
             if (colon == -1 || colon > spec.indexOf('/')) {
                 spec = spec.replaceFirst("/", ":/");
             }
         } catch(Exception ex) {
             return null;
         }
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -2082,17 +2082,17 @@ void
 AndroidBridge::SetPresentationSurface(EGLSurface aPresentationSurface)
 {
     mPresentationSurface = aPresentationSurface;
 }
 
 Object::LocalRef AndroidBridge::ChannelCreate(Object::Param stream) {
     JNIEnv* const env = GetEnvForThread();
     auto rv = Object::LocalRef::Adopt(env, env->CallStaticObjectMethod(
-            sBridge->jReadableByteChannel, sBridge->jChannelCreate, stream.Get()));
+            sBridge->jChannels, sBridge->jChannelCreate, stream.Get()));
     HandleUncaughtException(env);
     return rv;
 }
 
 void AndroidBridge::InputStreamClose(Object::Param obj) {
     JNIEnv* const env = GetEnvForThread();
     env->CallVoidMethod(obj.Get(), sBridge->jClose);
     HandleUncaughtException(env);