1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.browser;
18
19import android.app.ProgressDialog;
20import android.app.WallpaperManager;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.graphics.Bitmap;
24import android.graphics.BitmapFactory;
25import android.graphics.Canvas;
26import android.graphics.drawable.Drawable;
27import android.util.Log;
28import android.view.MenuItem;
29import android.view.MenuItem.OnMenuItemClickListener;
30import java.io.BufferedInputStream;
31import java.io.ByteArrayInputStream;
32import java.io.IOException;
33import java.io.InputStream;
34import java.net.MalformedURLException;
35import java.net.URL;
36
37/**
38 * Handle setWallpaper requests
39 *
40 */
41public class WallpaperHandler extends Thread
42        implements OnMenuItemClickListener, DialogInterface.OnCancelListener {
43
44    private static final String LOGTAG = "WallpaperHandler";
45    // This should be large enough for BitmapFactory to decode the header so
46    // that we can mark and reset the input stream to avoid duplicate network i/o
47    private static final int BUFFER_SIZE = 128 * 1024;
48
49    private Context mContext;
50    private String  mUrl;
51    private ProgressDialog mWallpaperProgress;
52    private boolean mCanceled = false;
53
54    public WallpaperHandler(Context context, String url) {
55        mContext = context;
56        mUrl = url;
57    }
58
59    @Override
60    public void onCancel(DialogInterface dialog) {
61        mCanceled = true;
62    }
63
64    @Override
65    public boolean onMenuItemClick(MenuItem item) {
66        if (mUrl != null && getState() == State.NEW) {
67            // The user may have tried to set a image with a large file size as
68            // their background so it may take a few moments to perform the
69            // operation.
70            // Display a progress spinner while it is working.
71            mWallpaperProgress = new ProgressDialog(mContext);
72            mWallpaperProgress.setIndeterminate(true);
73            mWallpaperProgress.setMessage(mContext.getResources()
74                    .getText(R.string.progress_dialog_setting_wallpaper));
75            mWallpaperProgress.setCancelable(true);
76            mWallpaperProgress.setOnCancelListener(this);
77            mWallpaperProgress.show();
78            start();
79        }
80        return true;
81    }
82
83    @Override
84    public void run() {
85        WallpaperManager wm = WallpaperManager.getInstance(mContext);
86        Drawable oldWallpaper = wm.getDrawable();
87        InputStream inputstream = null;
88        try {
89            // TODO: This will cause the resource to be downloaded again, when
90            // we should in most cases be able to grab it from the cache. To fix
91            // this we should query WebCore to see if we can access a cached
92            // version and instead open an input stream on that. This pattern
93            // could also be used in the download manager where the same problem
94            // exists.
95            inputstream = openStream();
96            if (inputstream != null) {
97                if (!inputstream.markSupported()) {
98                    inputstream = new BufferedInputStream(inputstream, BUFFER_SIZE);
99                }
100                inputstream.mark(BUFFER_SIZE);
101                BitmapFactory.Options options = new BitmapFactory.Options();
102                options.inJustDecodeBounds = true;
103                // We give decodeStream a wrapped input stream so it doesn't
104                // mess with our mark (currently it sets a mark of 1024)
105                BitmapFactory.decodeStream(
106                        new BufferedInputStream(inputstream), null, options);
107                int maxWidth = wm.getDesiredMinimumWidth();
108                int maxHeight = wm.getDesiredMinimumHeight();
109                // Give maxWidth and maxHeight some leeway
110                maxWidth *= 1.25;
111                maxHeight *= 1.25;
112                int bmWidth = options.outWidth;
113                int bmHeight = options.outHeight;
114
115                int scale = 1;
116                while (bmWidth > maxWidth || bmHeight > maxHeight) {
117                    scale <<= 1;
118                    bmWidth >>= 1;
119                    bmHeight >>= 1;
120                }
121                options.inJustDecodeBounds = false;
122                options.inSampleSize = scale;
123                try {
124                    inputstream.reset();
125                } catch (IOException e) {
126                    // BitmapFactory read more than we could buffer
127                    // Re-open the stream
128                    inputstream.close();
129                    inputstream = openStream();
130                }
131                Bitmap scaledWallpaper = BitmapFactory.decodeStream(inputstream,
132                        null, options);
133                wm.setBitmap(scaledWallpaper);
134            }
135        } catch (IOException e) {
136            Log.e(LOGTAG, "Unable to set new wallpaper");
137            // Act as though the user canceled the operation so we try to
138            // restore the old wallpaper.
139            mCanceled = true;
140        } finally {
141            if (inputstream != null) {
142                try {
143                    inputstream.close();
144                } catch (IOException e) {
145                    // Ignore
146                }
147            }
148        }
149
150        if (mCanceled) {
151            // Restore the old wallpaper if the user cancelled whilst we were
152            // setting
153            // the new wallpaper.
154            int width = oldWallpaper.getIntrinsicWidth();
155            int height = oldWallpaper.getIntrinsicHeight();
156            Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
157            Canvas canvas = new Canvas(bm);
158            oldWallpaper.setBounds(0, 0, width, height);
159            oldWallpaper.draw(canvas);
160            canvas.setBitmap(null);
161            try {
162                wm.setBitmap(bm);
163            } catch (IOException e) {
164                Log.e(LOGTAG, "Unable to restore old wallpaper.");
165            }
166            mCanceled = false;
167        }
168
169        if (mWallpaperProgress.isShowing()) {
170            mWallpaperProgress.dismiss();
171        }
172    }
173
174    /**
175     * Opens the input stream for the URL that the class should
176     * use to set the wallpaper. Abstracts the difference between
177     * standard URLs and data URLs.
178     * @return An open InputStream for the data at the URL
179     * @throws IOException if there is an error opening the URL stream
180     * @throws MalformedURLException if the URL is malformed
181     */
182    private InputStream openStream() throws IOException, MalformedURLException {
183        InputStream inputStream = null;
184        if (DataUri.isDataUri(mUrl)) {
185            DataUri dataUri = new DataUri(mUrl);
186            inputStream = new ByteArrayInputStream(dataUri.getData());
187        } else {
188            URL url = new URL(mUrl);
189            inputStream = url.openStream();
190        }
191        return inputStream;
192    }
193}
194