diff options
Diffstat (limited to 'core/java/android/app/WallpaperManager.java')
-rw-r--r-- | core/java/android/app/WallpaperManager.java | 709 |
1 files changed, 709 insertions, 0 deletions
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java new file mode 100644 index 0000000..e98b286 --- /dev/null +++ b/core/java/android/app/WallpaperManager.java @@ -0,0 +1,709 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * 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 android.app; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.ViewRoot; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Provides access to the system wallpaper. With WallpaperManager, you can + * get the current wallpaper, get the desired dimensions for the wallpaper, set + * the wallpaper, and more. Get an instance of WallpaperManager with + * {@link #getInstance(android.content.Context) getInstance()}. + */ +public class WallpaperManager { + private static String TAG = "WallpaperManager"; + private static boolean DEBUG = false; + private float mWallpaperXStep = -1; + private float mWallpaperYStep = -1; + + /** + * Launch an activity for the user to pick the current global live + * wallpaper. + */ + public static final String ACTION_LIVE_WALLPAPER_CHOOSER + = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER"; + + private final Context mContext; + + /** + * Special drawable that draws a wallpaper as fast as possible. Assumes + * no scaling or placement off (0,0) of the wallpaper (this should be done + * at the time the bitmap is loaded). + */ + static class FastBitmapDrawable extends Drawable { + private final Bitmap mBitmap; + private final int mWidth; + private final int mHeight; + private int mDrawLeft; + private int mDrawTop; + + private FastBitmapDrawable(Bitmap bitmap) { + mBitmap = bitmap; + mWidth = bitmap.getWidth(); + mHeight = bitmap.getHeight(); + setBounds(0, 0, mWidth, mHeight); + } + + @Override + public void draw(Canvas canvas) { + canvas.drawBitmap(mBitmap, mDrawLeft, mDrawTop, null); + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } + + @Override + public void setBounds(int left, int top, int right, int bottom) { + mDrawLeft = left + (right-left - mWidth) / 2; + mDrawTop = top + (bottom-top - mHeight) / 2; + } + + @Override + public void setBounds(Rect bounds) { + // TODO Auto-generated method stub + super.setBounds(bounds); + } + + @Override + public void setAlpha(int alpha) { + throw new UnsupportedOperationException( + "Not supported with this drawable"); + } + + @Override + public void setColorFilter(ColorFilter cf) { + throw new UnsupportedOperationException( + "Not supported with this drawable"); + } + + @Override + public void setDither(boolean dither) { + throw new UnsupportedOperationException( + "Not supported with this drawable"); + } + + @Override + public void setFilterBitmap(boolean filter) { + throw new UnsupportedOperationException( + "Not supported with this drawable"); + } + + @Override + public int getIntrinsicWidth() { + return mWidth; + } + + @Override + public int getIntrinsicHeight() { + return mHeight; + } + + @Override + public int getMinimumWidth() { + return mWidth; + } + + @Override + public int getMinimumHeight() { + return mHeight; + } + } + + static class Globals extends IWallpaperManagerCallback.Stub { + private IWallpaperManager mService; + private Bitmap mWallpaper; + private Bitmap mDefaultWallpaper; + + private static final int MSG_CLEAR_WALLPAPER = 1; + + private final Handler mHandler; + + Globals(Looper looper) { + IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE); + mService = IWallpaperManager.Stub.asInterface(b); + mHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_CLEAR_WALLPAPER: + synchronized (this) { + mWallpaper = null; + mDefaultWallpaper = null; + } + break; + } + } + }; + } + + public void onWallpaperChanged() { + /* The wallpaper has changed but we shouldn't eagerly load the + * wallpaper as that would be inefficient. Reset the cached wallpaper + * to null so if the user requests the wallpaper again then we'll + * fetch it. + */ + mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER); + } + + public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) { + synchronized (this) { + if (mWallpaper != null) { + return mWallpaper; + } + if (mDefaultWallpaper != null) { + return mDefaultWallpaper; + } + mWallpaper = getCurrentWallpaperLocked(context); + if (mWallpaper == null && returnDefault) { + mDefaultWallpaper = getDefaultWallpaperLocked(context); + return mDefaultWallpaper; + } + return mWallpaper; + } + } + + private Bitmap getCurrentWallpaperLocked(Context context) { + try { + Bundle params = new Bundle(); + ParcelFileDescriptor fd = mService.getWallpaper(this, params); + if (fd != null) { + int width = params.getInt("width", 0); + int height = params.getInt("height", 0); + + if (width <= 0 || height <= 0) { + // Degenerate case: no size requested, just load + // bitmap as-is. + Bitmap bm = BitmapFactory.decodeFileDescriptor( + fd.getFileDescriptor(), null, null); + try { + fd.close(); + } catch (IOException e) { + } + if (bm != null) { + bm.setDensity(DisplayMetrics.DENSITY_DEVICE); + } + return bm; + } + + // Load the bitmap with full color depth, to preserve + // quality for later processing. + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inDither = false; + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + Bitmap bm = BitmapFactory.decodeFileDescriptor( + fd.getFileDescriptor(), null, options); + try { + fd.close(); + } catch (IOException e) { + } + + return generateBitmap(context, bm, width, height); + } + } catch (RemoteException e) { + } + return null; + } + + private Bitmap getDefaultWallpaperLocked(Context context) { + try { + InputStream is = context.getResources().openRawResource( + com.android.internal.R.drawable.default_wallpaper); + if (is != null) { + int width = mService.getWidthHint(); + int height = mService.getHeightHint(); + + if (width <= 0 || height <= 0) { + // Degenerate case: no size requested, just load + // bitmap as-is. + Bitmap bm = BitmapFactory.decodeStream(is, null, null); + try { + is.close(); + } catch (IOException e) { + } + if (bm != null) { + bm.setDensity(DisplayMetrics.DENSITY_DEVICE); + } + return bm; + } + + // Load the bitmap with full color depth, to preserve + // quality for later processing. + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inDither = false; + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + Bitmap bm = BitmapFactory.decodeStream(is, null, options); + try { + is.close(); + } catch (IOException e) { + } + + return generateBitmap(context, bm, width, height); + } + } catch (RemoteException e) { + } + return null; + } + } + + private static Object mSync = new Object(); + private static Globals sGlobals; + + static void initGlobals(Looper looper) { + synchronized (mSync) { + if (sGlobals == null) { + sGlobals = new Globals(looper); + } + } + } + + /*package*/ WallpaperManager(Context context, Handler handler) { + mContext = context; + initGlobals(context.getMainLooper()); + } + + /** + * Retrieve a WallpaperManager associated with the given Context. + */ + public static WallpaperManager getInstance(Context context) { + return (WallpaperManager)context.getSystemService( + Context.WALLPAPER_SERVICE); + } + + /** @hide */ + public IWallpaperManager getIWallpaperManager() { + return sGlobals.mService; + } + + /** + * Retrieve the current system wallpaper; if + * no wallpaper is set, the system default wallpaper is returned. + * This is returned as an + * abstract Drawable that you can install in a View to display whatever + * wallpaper the user has currently set. + * + * @return Returns a Drawable object that will draw the wallpaper. + */ + public Drawable getDrawable() { + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true); + if (bm != null) { + Drawable dr = new BitmapDrawable(mContext.getResources(), bm); + dr.setDither(false); + return dr; + } + return null; + } + + /** + * Retrieve the current system wallpaper; if there is no wallpaper set, + * a null pointer is returned. This is returned as an + * abstract Drawable that you can install in a View to display whatever + * wallpaper the user has currently set. + * + * @return Returns a Drawable object that will draw the wallpaper or a + * null pointer if these is none. + */ + public Drawable peekDrawable() { + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false); + if (bm != null) { + Drawable dr = new BitmapDrawable(mContext.getResources(), bm); + dr.setDither(false); + return dr; + } + return null; + } + + /** + * Like {@link #getDrawable()}, but the returned Drawable has a number + * of limitations to reduce its overhead as much as possible. It will + * never scale the wallpaper (only centering it if the requested bounds + * do match the bitmap bounds, which should not be typical), doesn't + * allow setting an alpha, color filter, or other attributes, etc. The + * bounds of the returned drawable will be initialized to the same bounds + * as the wallpaper, so normally you will not need to touch it. The + * drawable also assumes that it will be used in a context running in + * the same density as the screen (not in density compatibility mode). + * + * @return Returns a Drawable object that will draw the wallpaper. + */ + public Drawable getFastDrawable() { + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true); + if (bm != null) { + Drawable dr = new FastBitmapDrawable(bm); + return dr; + } + return null; + } + + /** + * Like {@link #getFastDrawable()}, but if there is no wallpaper set, + * a null pointer is returned. + * + * @return Returns an optimized Drawable object that will draw the + * wallpaper or a null pointer if these is none. + */ + public Drawable peekFastDrawable() { + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false); + if (bm != null) { + Drawable dr = new FastBitmapDrawable(bm); + return dr; + } + return null; + } + + /** + * If the current wallpaper is a live wallpaper component, return the + * information about that wallpaper. Otherwise, if it is a static image, + * simply return null. + */ + public WallpaperInfo getWallpaperInfo() { + try { + return sGlobals.mService.getWallpaperInfo(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Change the current system wallpaper to the bitmap in the given resource. + * The resource is opened as a raw data stream and copied into the + * wallpaper; it must be a valid PNG or JPEG image. On success, the intent + * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. + * + * @param resid The bitmap to save. + * + * @throws IOException If an error occurs reverting to the default + * wallpaper. + */ + public void setResource(int resid) throws IOException { + try { + Resources resources = mContext.getResources(); + /* Set the wallpaper to the default values */ + ParcelFileDescriptor fd = sGlobals.mService.setWallpaper( + "res:" + resources.getResourceName(resid)); + if (fd != null) { + FileOutputStream fos = null; + try { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + setWallpaper(resources.openRawResource(resid), fos); + } finally { + if (fos != null) { + fos.close(); + } + } + } + } catch (RemoteException e) { + } + } + + /** + * Change the current system wallpaper to a bitmap. The given bitmap is + * converted to a PNG and stored as the wallpaper. On success, the intent + * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. + * + * @param bitmap The bitmap to save. + * + * @throws IOException If an error occurs reverting to the default + * wallpaper. + */ + public void setBitmap(Bitmap bitmap) throws IOException { + try { + ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null); + if (fd == null) { + return; + } + FileOutputStream fos = null; + try { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos); + } finally { + if (fos != null) { + fos.close(); + } + } + } catch (RemoteException e) { + } + } + + /** + * Change the current system wallpaper to a specific byte stream. The + * give InputStream is copied into persistent storage and will now be + * used as the wallpaper. Currently it must be either a JPEG or PNG + * image. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} + * is broadcast. + * + * @param data A stream containing the raw data to install as a wallpaper. + * + * @throws IOException If an error occurs reverting to the default + * wallpaper. + */ + public void setStream(InputStream data) throws IOException { + try { + ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null); + if (fd == null) { + return; + } + FileOutputStream fos = null; + try { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + setWallpaper(data, fos); + } finally { + if (fos != null) { + fos.close(); + } + } + } catch (RemoteException e) { + } + } + + private void setWallpaper(InputStream data, FileOutputStream fos) + throws IOException { + byte[] buffer = new byte[32768]; + int amt; + while ((amt=data.read(buffer)) > 0) { + fos.write(buffer, 0, amt); + } + } + + /** + * Returns the desired minimum width for the wallpaper. Callers of + * {@link #setBitmap(android.graphics.Bitmap)} or + * {@link #setStream(java.io.InputStream)} should check this value + * beforehand to make sure the supplied wallpaper respects the desired + * minimum width. + * + * If the returned value is <= 0, the caller should use the width of + * the default display instead. + * + * @return The desired minimum width for the wallpaper. This value should + * be honored by applications that set the wallpaper but it is not + * mandatory. + */ + public int getDesiredMinimumWidth() { + try { + return sGlobals.mService.getWidthHint(); + } catch (RemoteException e) { + // Shouldn't happen! + return 0; + } + } + + /** + * Returns the desired minimum height for the wallpaper. Callers of + * {@link #setBitmap(android.graphics.Bitmap)} or + * {@link #setStream(java.io.InputStream)} should check this value + * beforehand to make sure the supplied wallpaper respects the desired + * minimum height. + * + * If the returned value is <= 0, the caller should use the height of + * the default display instead. + * + * @return The desired minimum height for the wallpaper. This value should + * be honored by applications that set the wallpaper but it is not + * mandatory. + */ + public int getDesiredMinimumHeight() { + try { + return sGlobals.mService.getHeightHint(); + } catch (RemoteException e) { + // Shouldn't happen! + return 0; + } + } + + /** + * For use only by the current home application, to specify the size of + * wallpaper it would like to use. This allows such applications to have + * a virtual wallpaper that is larger than the physical screen, matching + * the size of their workspace. + * @param minimumWidth Desired minimum width + * @param minimumHeight Desired minimum height + */ + public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) { + try { + sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight); + } catch (RemoteException e) { + } + } + + /** + * Set the position of the current wallpaper within any larger space, when + * that wallpaper is visible behind the given window. The X and Y offsets + * are floating point numbers ranging from 0 to 1, representing where the + * wallpaper should be positioned within the screen space. These only + * make sense when the wallpaper is larger than the screen. + * + * @param windowToken The window who these offsets should be associated + * with, as returned by {@link android.view.View#getWindowToken() + * View.getWindowToken()}. + * @param xOffset The offset along the X dimension, from 0 to 1. + * @param yOffset The offset along the Y dimension, from 0 to 1. + */ + public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) { + try { + //Log.v(TAG, "Sending new wallpaper offsets from app..."); + ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( + windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep); + //Log.v(TAG, "...app returning after sending offsets!"); + } catch (RemoteException e) { + // Ignore. + } + } + + /** + * For applications that use multiple virtual screens showing a wallpaper, + * specify the step size between virtual screens. For example, if the + * launcher has 5 virtual screens, it would specify an xStep of 0.5, + * since the X offset for those screens are 0.0, 0.5 and 1.0 + * @param xStep The X offset delta from one screen to the next one + * @param yStep The Y offset delta from one screen to the next one + */ + public void setWallpaperOffsetSteps(float xStep, float yStep) { + mWallpaperXStep = xStep; + mWallpaperYStep = yStep; + } + + /** + * Send an arbitrary command to the current active wallpaper. + * + * @param windowToken The window who these offsets should be associated + * with, as returned by {@link android.view.View#getWindowToken() + * View.getWindowToken()}. + * @param action Name of the command to perform. This must be a scoped + * name to avoid collisions, such as "com.mycompany.wallpaper.DOIT". + * @param x Arbitrary integer argument based on command. + * @param y Arbitrary integer argument based on command. + * @param z Arbitrary integer argument based on command. + * @param extras Optional additional information for the command, or null. + */ + public void sendWallpaperCommand(IBinder windowToken, String action, + int x, int y, int z, Bundle extras) { + try { + //Log.v(TAG, "Sending new wallpaper offsets from app..."); + ViewRoot.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand( + windowToken, action, x, y, z, extras, false); + //Log.v(TAG, "...app returning after sending offsets!"); + } catch (RemoteException e) { + // Ignore. + } + } + + /** + * Clear the offsets previously associated with this window through + * {@link #setWallpaperOffsets(IBinder, float, float)}. This reverts + * the window to its default state, where it does not cause the wallpaper + * to scroll from whatever its last offsets were. + * + * @param windowToken The window who these offsets should be associated + * with, as returned by {@link android.view.View#getWindowToken() + * View.getWindowToken()}. + */ + public void clearWallpaperOffsets(IBinder windowToken) { + try { + ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( + windowToken, -1, -1, -1, -1); + } catch (RemoteException e) { + // Ignore. + } + } + + /** + * Remove any currently set wallpaper, reverting to the system's default + * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} + * is broadcast. + * + * @throws IOException If an error occurs reverting to the default + * wallpaper. + */ + public void clear() throws IOException { + setResource(com.android.internal.R.drawable.default_wallpaper); + } + + static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) { + if (bm == null) { + return bm; + } + bm.setDensity(DisplayMetrics.DENSITY_DEVICE); + + // This is the final bitmap we want to return. + // XXX We should get the pixel depth from the system (to match the + // physical display depth), when there is a way. + Bitmap newbm = Bitmap.createBitmap(width, height, + Bitmap.Config.RGB_565); + newbm.setDensity(DisplayMetrics.DENSITY_DEVICE); + Canvas c = new Canvas(newbm); + c.setDensity(DisplayMetrics.DENSITY_DEVICE); + Rect targetRect = new Rect(); + targetRect.left = targetRect.top = 0; + targetRect.right = bm.getWidth(); + targetRect.bottom = bm.getHeight(); + + int deltaw = width - targetRect.right; + int deltah = height - targetRect.bottom; + + if (deltaw > 0 || deltah > 0) { + // We need to scale up so it covers the entire + // area. + float scale = 1.0f; + if (deltaw > deltah) { + scale = width / (float)targetRect.right; + } else { + scale = height / (float)targetRect.bottom; + } + targetRect.right = (int)(targetRect.right*scale); + targetRect.bottom = (int)(targetRect.bottom*scale); + deltaw = width - targetRect.right; + deltah = height - targetRect.bottom; + } + + targetRect.offset(deltaw/2, deltah/2); + Paint paint = new Paint(); + paint.setFilterBitmap(true); + paint.setDither(true); + c.drawBitmap(bm, null, targetRect, paint); + + bm.recycle(); + return newbm; + } +} |