mobisocial.musubi.service.WebRenderService.java Source code

Java tutorial

Introduction

Here is the source code for mobisocial.musubi.service.WebRenderService.java

Source

/*
 * Copyright 2012 The Stanford MobiSocial Laboratory
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package mobisocial.musubi.service;

import gnu.trove.list.array.TByteArrayList;

import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Random;

import mobisocial.musubi.R;
import mobisocial.musubi.objects.AppStateObj;
import mobisocial.musubi.util.Util;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Picture;
import android.os.Binder;
import android.os.IBinder;
import android.support.v4.util.LruCache;
import android.util.DisplayMetrics;
import android.view.View.MeasureSpec;
import android.webkit.WebView;
import android.webkit.WebView.PictureListener;
import android.webkit.WebViewClient;
import android.widget.ImageView;
import android.widget.LinearLayout;

//TODO: use this to measure
public class WebRenderService extends Service {
    public static final String TAG = "WebRenderService";

    public interface SizeReceiver {
        //note this is the size on the local phone and generally
        //we should be processing it and sending it with something that
        //understands any relevant scaling aspect ratio
        public void onSizeComputed(int width, int height);
    }

    WebView mWebView;
    RenderWebViewClient mWebViewClient;
    static WebRenderService sService = null;
    //this is a cheap way to workaround the fact that our current system is calling bind view twice
    //per object when it loads.  we would like to cache these rendered images offline, but for now
    //i would like to avoid additional sources of space usage.
    LruCache<TByteArrayList, SoftReference<Bitmap>> mWebViewCache = new LruCache<TByteArrayList, SoftReference<Bitmap>>(
            8);

    public static class WebRenderRequest {
        // input
        String mHtml;
        ImageView mDestionationView;
        int targetWidth;
        int targetHeight;

        // output
        int mWidth;
        int mHeight;
    }

    static LinkedList<WebRenderRequest> sToProcess = new LinkedList<WebRenderRequest>();

    class RenderWebViewClient extends WebViewClient {
        WebRenderRequest mCurrent;
        private boolean mPending;

        public RenderWebViewClient() {
            mWebView.setPictureListener(new PictureListener() {
                @Override
                public void onNewPicture(WebView view, Picture picture) {
                    if (mCurrent == null || picture.getWidth() == 0 || picture.getHeight() == 0) {
                        return;
                    }
                    mCurrent.mWidth = picture.getWidth();
                    mCurrent.mHeight = picture.getHeight();
                    float scale = (float) mCurrent.targetHeight / picture.getHeight();

                    Bitmap bitmap = Bitmap.createBitmap((int) (scale * mCurrent.mWidth),
                            (int) (scale * mCurrent.mHeight), Bitmap.Config.RGB_565);
                    Canvas canvas = new Canvas(bitmap);
                    canvas.scale(scale, scale);
                    picture.draw(canvas);
                    mWebViewCache.put(new TByteArrayList(Util.sha256(mCurrent.mHtml.getBytes())),
                            new SoftReference<Bitmap>(bitmap));
                    if (mCurrent.mDestionationView != null) {
                        setImageViewBitmapAndLayout(mCurrent.mDestionationView, bitmap);
                    }
                    mCurrent = null;
                    mPending = false;
                    WebRenderRequest item = sToProcess.peek();
                    if (item != null) {
                        kickoffJob(item);
                    }
                }
            });
        }

        public void addItem(WebRenderRequest item) {
            sToProcess.add(item);
            if (!mPending) {
                kickoffJob(item);
            }
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            mCurrent = sToProcess.poll();
        }

        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
        }

        @Override
        public void onScaleChanged(WebView view, float oldScale, float newScale) {
        }

        boolean lastWasSpacer = false;

        void kickoffJob(WebRenderRequest item) {
            mWebView.clearView();
            mWebView.clearHistory(); //otherwise this leaks cached copies of the page in memory
            mPending = true;
            mWebView.measure(MeasureSpec.makeMeasureSpec((int) (1), MeasureSpec.UNSPECIFIED),
                    MeasureSpec.makeMeasureSpec(AppStateObj.MAX_HEIGHT, MeasureSpec.UNSPECIFIED));
            mWebView.layout(0, 0, 1, 1);
            if (!lastWasSpacer) {
                WebRenderRequest dummy = new WebRenderRequest();
                dummy.mHtml = "<html><body>" + new Random().nextLong() + "</body></html>";
                dummy.targetHeight = 1;
                dummy.targetWidth = 1;
                sToProcess.add(0, dummy);
                lastWasSpacer = true;
                mWebView.loadData(dummy.mHtml, "text/html", "UTF-8");
            } else {
                lastWasSpacer = false;
                mWebView.loadData(item.mHtml, "text/html", "UTF-8");
            }
        }
    }

    static class WebRenderServiceConnection implements ServiceConnection {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            sService = null;
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            sService = ((WebRenderServiceBinder) service).getService();
            if (sToProcess.size() > 0) {
                sService.mWebViewClient.kickoffJob(sToProcess.peek());
            }
        }
    }

    static void bindAndSaveService(Context owner) {
        owner.bindService(new Intent(owner, WebRenderService.class), new WebRenderServiceConnection(),
                BIND_AUTO_CREATE);
    }

    public static ImageView newLazyImageWeb(Context context, String html, int targetWidth, int targetHeight) {
        ImageView iv = new ImageView(context);
        //try the short cache
        if (sService != null) {
            SoftReference<Bitmap> srb = sService.mWebViewCache
                    .get(new TByteArrayList(Util.sha256(html.getBytes())));
            if (srb != null) {
                Bitmap b = srb.get();
                if (b != null) {
                    sService.setImageViewBitmapAndLayout(iv, b);
                    return iv;
                }
            }
        }
        //set a default?
        iv.setImageBitmap(Bitmap.createBitmap(1, 1, Config.RGB_565));
        WebRenderRequest req = new WebRenderRequest();
        req.targetHeight = targetHeight;
        req.targetWidth = targetWidth;
        req.mDestionationView = iv;
        req.mHtml = html;
        if (sService != null) {
            sService.mWebViewClient.addItem(req);
        } else {
            sToProcess.add(req);
        }
        return iv;
    }

    public void measureAndLayout() {
        //fake pump the layout
        mWebView.measure(MeasureSpec.makeMeasureSpec(1, MeasureSpec.UNSPECIFIED),
                MeasureSpec.makeMeasureSpec(AppStateObj.MAX_HEIGHT, MeasureSpec.UNSPECIFIED));
        mWebView.layout(0, 0, 1, AppStateObj.MAX_HEIGHT);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mWebView = new WebView(this);
        //set the size before we start
        measureAndLayout();
        mWebViewClient = new RenderWebViewClient();
        mWebView.setWebViewClient(mWebViewClient);
    }

    @Override
    public void onDestroy() {
        mWebView.destroy();
        mWebView = null;
        mWebViewClient = null;
        super.onDestroy();
    }

    public class WebRenderServiceBinder extends Binder {
        public WebRenderService getService() {
            return WebRenderService.this;
        }
    }

    private final IBinder mBinder = new WebRenderServiceBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private void setImageViewBitmapAndLayout(ImageView imageView, Bitmap bitmap) {
        float scaleFactor;
        if (getResources().getBoolean(R.bool.is_tablet)) {
            scaleFactor = 3.0f;
        } else {
            scaleFactor = 2.0f;
        }
        DisplayMetrics dm = getResources().getDisplayMetrics();
        int pixels = dm.widthPixels;
        if (dm.heightPixels < pixels) {
            pixels = dm.heightPixels;
        }
        int width = (int) (pixels / scaleFactor);
        int height = (int) ((float) width / bitmap.getWidth() * bitmap.getHeight());
        int max_height = (int) (AppStateObj.MAX_HEIGHT * dm.density);
        if (height > max_height) {
            width = width * max_height / height;
            height = max_height;
        }
        imageView.setLayoutParams(new LinearLayout.LayoutParams(width, height));
        imageView.setImageBitmap(bitmap);
    }
}