《学习笔记》android6.0 锁屏壁纸功能

Posted 王_健

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《学习笔记》android6.0 锁屏壁纸功能相关的知识,希望对你有一定的参考价值。

1.需求

Three entrance for this feature. one is in Home screen, the second is in Settings>Display screen, the third one is in Gallery>single view a picture>Set picture as>Wallaper screen.

2.方案

思路:

通过对WallpaperManager和SystemUI中添加对应的方法,来实现对外提供设置锁屏壁纸的接口。

步骤:
1.设置锁屏壁纸,将选好的壁纸存入SystemUI对应目录下;
2.读取锁屏壁纸,当屏幕点亮后从SystemUI对应目录下读取图片对当前锁屏界面进行背景的设置,达到效果。

代码路径:
frameworks/base/core/java/android/app/WallpaperManager.java
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java

具体实现代码:

/**
 * 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().
 *
 * <p> An app can check whether wallpapers are supported for the current user, by calling
 * @link #isWallpaperSupported().
 */
public class WallpaperManager 
    private static String TAG = "WallpaperManager";
    private static boolean DEBUG = true;
    private static final String WALLPAPERDIR = "/data/data/com.android.systemui/";
    static final String MENUWALLPAPERNAME = "menuwallpaper.png";
    static final String LOCKWALLPAPERNAME = "lockwallpaper.png";
    /** @hide */
    public static final int SCREEN_WP_MODE = 0;
    /** @hide */
    public static final int LOCK_WP_MODE = 1;

    /** @hide */
    public static final int ALL_WP_MODE = 2;

    private final Context mContext;

    <中间部分省略>...

    //默认设置壁纸的方法
    public void setStream(InputStream data) throws IOException 
        if (sGlobals.mService == null) 
            Log.w(TAG, "WallpaperService not running");
            return;
        
        try 
            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
                    mContext.getOpPackageName());
            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) 
            // Ignore
        
    
    //将选好的图片流写到指定路径
    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);
        
    


    //通过指定图片文件路径,得到一个文件描述符对象
    private ParcelFileDescriptor getLockWallpaperPath() 
        File dir = new File(WALLPAPERDIR);
        if(!dir.exists())
          dir.mkdirs();
        
        FileUtils.setPermissions(WALLPAPERDIR,0777,-1,-1);

        File file = null;
        file = new File(dir, LOCKWALLPAPERNAME);
        try 
            file.createNewFile();
         catch (IOException io) 
            io.printStackTrace();
        
        ParcelFileDescriptor fd=null;
        try
            fd = ParcelFileDescriptor.open(file,ParcelFileDescriptor.MODE_CREATE|ParcelFileDescriptor.MODE_READ_WRITE);
        catch(Exception e) 
            e.printStackTrace();
        
        return fd;
    

    //设置锁屏壁纸,将图片存入到指定路径下
    public void setLockStream(InputStream data) throws IOException 
        Log.w(TAG, "setLockStream...");
        if (sGlobals.mService == null) 
            return;
        
        if(data==null)
        
            //如果图片不存在,就以默认壁纸作为锁屏壁纸来设置
            data=sGlobals.openDefaultWallpaperRes(mContext);
        
        try 
            ParcelFileDescriptor fd = getLockWallpaperPath();
            if (fd == null) 
                return;
            
            FileOutputStream fos = null;
            try 
                fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
                setWallpaper(data, fos);
             finally 
                if (fos != null) 
                    fos.close();
                
            
         catch (Exception e) 
            e.printStackTrace();
        
        FileUtils.setPermissions(WALLPAPERDIR+"/"+LOCKWALLPAPERNAME,0777,-1,-1);
    

    //这个方法的两个参数应该是一样的。
    //应用场景:一张图片同事被设置成壁纸和锁屏壁纸。
    public void setAllStream(InputStream data_home,InputStream data_lock) throws IOException 
        if (sGlobals.mService == null) 
            return;
        
        if(data_home == null) 
            data_home=sGlobals.openDefaultWallpaperRes(mContext);
        else if(data_lock == null) 
            data_lock = sGlobals.openDefaultWallpaperRes(mContext);
        
        try 

            ParcelFileDescriptor fd_lock = getLockWallpaperPath();
            ParcelFileDescriptor fd_home = sGlobals.mService.setWallpaper(null,
                    mContext.getOpPackageName());

            if (fd_lock != null) 
                FileOutputStream fos_lock = null;
                try 
                    fos_lock = new ParcelFileDescriptor.AutoCloseOutputStream(fd_lock);
                    setWallpaper(data_lock, fos_lock);
                 finally 
                    if (fos_lock != null) 
                        fos_lock.close();
                    
                
            else if(fd_home != null)
                try 
                    fos_home = new ParcelFileDescriptor.AutoCloseOutputStream(fd_home);
                    setWallpaper(data_home, fos_home);
                 finally 
                    if (fos_home != null) 
                        fos_home.close();
                    
                
            

         catch (Exception e) 
            e.printStackTrace();
        
    

    //上面是存入的过程,这个方法是得到锁屏壁纸,用于设置背景。
    public Bitmap getCurrentLockWallpaper() 
        try 
            File file = new File(WALLPAPERDIR, LOCKWALLPAPERNAME);
            ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
            if (fd != null) 
                try 
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    Bitmap bm = BitmapFactory.decodeFileDescriptor(
                            fd.getFileDescriptor(), null, options);
                    return bm;
                 catch (OutOfMemoryError e) 
                 finally 
                    try 
                        fd.close();
                     catch (IOException e) 

                    
                
            
         catch (Exception e) 

        
        return null;
    

在WallpaperManager类中,仿照默认设置壁纸方法,添加了两个方法。setLockStream()(单独对锁屏设置背景)和setAllStream()(将同一张图片分别应用于待机壁纸和锁屏壁纸),同时添加了对锁屏壁纸获取的方法 getCurrentLockWallpaper();

PhoneStatusBar.java管理着PanelHolder 以及 NotificationPanelView下的各个控件,showKeyguard 方法和 hideKeyguard方法中都调用了updateKeyguardState()来实时更新锁屏状态和调整各控件的位置大小。具体锁屏结构和流程有时间再整理。
附上代码:

public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
        DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
        HeadsUpManager.OnHeadsUpChangedListener 

    ... ...

    private void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) 
        if (mState == StatusBarState.KEYGUARD) 
            mKeyguardIndicationController.setVisible(true);
            mNotificationPanel.resetViews();
            mKeyguardUserSwitcher.setKeyguard(true, fromShadeLocked);
            mStatusBarView.removePendingHideExpandedRunnables();
         else 
            mKeyguardIndicationController.setVisible(false);
            mKeyguardUserSwitcher.setKeyguard(false,
                    goingToFullShade || mState == StatusBarState.SHADE_LOCKED || fromShadeLocked);
        
        //如果当前mState 处于锁屏状态,通过wallpaperManager.getCurrentLockWallpaper取出图片资源作为PanelHolder的背景。
        if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) 
            Bitmap bm = getCurrentWallpaperLocked();
            if (bm != null) 
            Drawable dr = new BitmapDrawable(mContext.getResources(), generateBitmap(bm, 480, 1280));
            dr.setDither(false);
            mHolder.setBackground(dr);
            else
            
                mHolder.setBackgroundResource(0);
            
            mScrimController.setKeyguardShowing(true);
            mIconPolicy.setKeyguardShowing(true);
         else 
        //如果mState 为其他状态,比如待机状态等,将PanelHolder背景清除。否则会出现状态栏花屏现象,因为此时PanelHolder的大小即为状态栏大小,无法呈现图片。
            mHolder.setBackgroundResource(0);
            mScrimController.setKeyguardShowing(false);
            mIconPolicy.setKeyguardShowing(false);
        
        mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
        updateDozingState();
        updatePublicMode();
        updateStackScrollerState(goingToFullShade);
        updateNotifications();
        checkBarModes();

        /// M: Support "Operator plugin - Customize Carrier Label for PLMN". @
        updateCarrierLabelVisibility(false);
        /// M: Support "Operator plugin - Customize Carrier Label for PLMN". @

        updateMediaMetaData(false);
        mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
                mStatusBarKeyguardViewManager.isSecure());
    

    private Bitmap getCurrentWallpaperLocked() 
        WallpaperManager wallpaperManager = WallpaperManager.getInstance(mContext);
        return wallpaperManager.getCurrentLockWallpaper();
    

   //通过给定的长宽比对Bitmap图片进行缩放,铺满屏幕
    private Bitmap generateBitmap(Bitmap bm, int width, int height) 
        if (bm == null) 
            return null;
        

        bm.setDensity(DisplayMetrics.DENSITY_HIGH);

        if (width <= 0 || height <= 0
                || (bm.getWidth() == width && bm.getHeight() == height)) 
            return bm;
        

        // This is the final bitmap we want to return.
        try 
            Bitmap newbm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            newbm.setDensity(DisplayMetrics.DENSITY_HIGH);

            Canvas c = new Canvas(newbm);
            Rect targetRect = new Rect();
            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;
                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.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
            c.drawBitmap(bm, null, targetRect, paint);

            bm.recycle();
            c.setBitmap(null);
            return newbm;
         catch (OutOfMemoryError e) 
            return bm;
        
    

这个时候已经将锁屏壁纸一些基础功能完成,目前已经可以通过调用wallpaperManager.java的方法对待机壁纸和锁屏壁纸进行分别设置了。

3.例子

接下来通过对原生图库进行修改,效果如下:

用自定义pupopWindow弹框替代原生直接设置壁纸的流程,然后将通过点击选项触发相应的操作。
代码路径:
packages/apps/Launcher3/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
packages/apps/Launcher3/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java

 public class WallpaperCropActivity extends BaseActivity implements Handler.Callback 

     protected void init() 

           ...
        //set wallpaper Button
        final ActionBar actionBar = getActionBar();
        actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
        actionBar.getCustomView().setOnClickListener(
                new View.OnClickListener() 
                    @Override
                    public void onClick(View v) 
            //会有弹窗,所以这里不需要避免多次点击。
                        //mSetWallpaperButton.setEnabled(false);

                        boolean finishActivityWhenDone = true;

                        BitmapCropTask.OnBitmapCroppedHandler h =
                              new BitmapCropTask.OnBitmapCroppedHandler() 

                        public void onBitmapCropped(byte[] imageBytes) 

                            Point thumbSize = getDefaultThumbnailSize(
                                WallpaperCropActivity.this.getResources());

                            Bitmap thumb = createThumbnail(
                                    thumbSize, null, null, imageBytes, null, 0, 0, true);

                            mSavedWallpaper.writeImage(thumb, imageBytes);
                        
                    ;
                    //从图库进入这个界面,mWallpaperMode 初始值为-1.
                    if(mWallpaperMode < 0) 

                    //执行 自定义pupopWindow。
                        OperationPopupWindow pw = new OperationPopupWindow(WallpaperCropActivity.this,imageUri, h, finishActivityWhenDone);

                        pw.show(WallpaperCropActivity.this);
                    else

                        //从主页壁纸设置入口进入的,mWallpaperMode 是指定好的;
                        //直接根据mWallpaperMode 来设置壁纸类型。
                        cropImageAndSetWallpaper(imageUri, h, finishActivityWhenDone);
                    

                    ///M.
                    
                );
       ....
//GellaryBottomPopupWindow 就不贴出来了,就是继承了PupopWindow,
public class OperationPopupWindow extends GellaryBottomPopupWindow<Void> 

        public Uri uri = null;
        public BitmapCropTask.OnBitmapCroppedHandler onBitmapCroppedHandler = null;
        public boolean finishActivityWhenDone ;
        public OperationPopupWindow(Context context,Uri uri,
            BitmapCropTask.OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) 
            super(context, null);
            this.uri = uri;
            this.onBitmapCroppedHandler =onBitmapCroppedHandler;
            this.finishActivityWhenDone = finishActivityWhenDone;
        
        public void onsave()
           //最终执行的方法,为了适应屏幕对图片进行剪切,然后进入设置壁纸的流程
            cropImageAndSetWallpaper(uri, onBitmapCroppedHandler, finishActivityWhenDone);
        
        @Override
        protected View generateCustomView(Void data) 
            View root = View.inflate(context, R.layout.popup_wallpaper_opreation_gellary, null);
            View homeView = root.findViewById(R.id.home);
            homeView.setOnClickListener(new View.OnClickListener() 
                @Override
                public void onClick(View v) 
                 dismiss();
                 // Ensure that a tile is slelected and loaded.
                 mWallpaperMode = 0;           
                 onsave();
                
            );
            View lockView = root.findViewById(R.id.lock);
            lockView.setOnClickListener(new View.OnClickListener() 
                @Override
                public void onClick(View v) 
                    dismiss();
                    mWallpaperMode = 1;           
                    onsave();
                
            );
            View allView = root.findViewById(R.id.all);
            allView.setOnClickListener(new View.OnClickListener() 

                @Override
                public void onClick(View v) 
                     mWallpaperMode = 2;           
                     onsave();
                
            );
            View cancelView = root.findViewById(R.id.cancel);
            cancelView.setOnClickListener(new View.OnClickListener() 
                @Override
                public void onClick(View v) 
                    dismiss();
                
            );
            return root;
        

        ...

    protected void cropImageAndSetWallpaper(Uri uri, BitmapCropTask.OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) 

        ...
        //6.0上应该首次用BitmapCropTask 这种异步任务将耗时操作分离了出来。
        BitmapCropTask cropTask = new BitmapCropTask(getContext(), uri,
                cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
        cropTask.setCropSize(outWidth, outHeight);
        if (onBitmapCroppedHandler != null) 
            cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
        

        cropTask.setWallpaperMode(mWallpaperMode);
        cropTask.execute();
    


cropTask.setWallpaperMode()方法是为了执行不同的壁纸设置新增加的,接下来看看 BitmapCropTask.java文件,只看关键代码:

public class BitmapCropTask extends AsyncTask<Void, Void, Boolean> 
    ...

    //添加的方法,mWallpaperMode 对应三种壁纸设置模式,默认是待机壁纸。
    public void setWallpaperMode(int wpMode)
        mWallpaperMode = wpMode;
    

    @Override
    protected Boolean doInBackground(Void... params) 
        return cropBitmap(mWallpaperMode); //上段代码中最后会执行cropTask.execute(),走到这里;
    

    @Override
    protected void onPostExecute(Boolean result) 
      ...
    

    public boolean cropBitmap(int flag) 
        boolean failure = false;

        //M. ALPS01885181, sync with gallery, check the image size.
        if (isOutOfSpecLimit()) 
            Log.i(LOGTAG, "cropBitmap,image out of spec limit, mInUri:" + mInUri);
            return failure;
        
        ///M.

        //到这里终于看见了WallpaperManager。
        WallpaperManager wallpaperManager = null;
        //mSetWallpaper = true
        if (mSetWallpaper) 
            wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
        

        //由于壁纸设置入口很多,mNoCrop在本次流程中为false,留给其他方法调用的;
        if (mSetWallpaper && mNoCrop)  
            try 
                InputStream is_home = regenerateInputStream();
                InputStream is_lock = regenerateInputStream();

                if (is_home != null && is_lock != null) 
                    if(flag == WallpaperManager.LOCK_WP_MODE)
                        wallpaperManager.setLockStream(is_lock);
                    else if(flag == WallpaperManager.ALL_WP_MODE)
                        wallpaperManager.setAllStream(is_home,is_lock);
                    else
                        wallpaperManager.setStream(is_home);
                    

                    Utils.closeSilently(is_home);
                    Utils.closeSilently(is_lock);
                
             catch (IOException e) 
              ...
            
            return !failure;
         else 
                 ...

                 //一大堆算法,随后剪切缩放得出适应屏幕大小的图片资源
                 crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
                          roundedTrueCrop.top, roundedTrueCrop.width(),
                          roundedTrueCrop.height());

            

            if (crop == null) 
                Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
                failure = true;
                return false;
            
            if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) 

                ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);

                //将bitmap压缩成ByteArrayOutputStream;
                if (crop.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, tmpOut)) 
                    // If we need to set to the wallpaper, set it
                    if (mSetWallpaper && wallpaperManager != null) 
                        try 

                            byte[] outByteArray = tmpOut.toByteArray();
                            if(flag == WallpaperManager.LOCK_WP_MODE)

                            //仅设置锁屏壁纸   
                            wallpaperManager.setLockStream(new ByteArrayInputStream(outByteArray));
                            else if(flag == WallpaperManager.ALL_WP_MODE)

                            //同时设置锁屏和待机壁纸   
                            wallpaperManager.setAllStream(new ByteArrayInputStream(outByteArray),new ByteArrayInputStream(outByteArray));
                            else

                            //设置默认壁纸
                            wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
                            
                            if (mOnBitmapCroppedHandler != null) 
                                mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
                            
                         catch (IOException e) 
                            Log.w(LOGTAG, "cannot write stream to wallpaper", e);
                            failure = true;
                        
                    
                 else 
                    Log.w(LOGTAG, "cannot compress bitmap");
                    failure = true;
                
        
        return !failure; // True if any of the operations failed
    

核心流程就是这样,其他细节不做笔记了。对于Keyguard也是一知半解,下次接着写Keyguard流程,巩固知识!

以上是关于《学习笔记》android6.0 锁屏壁纸功能的主要内容,如果未能解决你的问题,请参考以下文章

如何在电脑里设置锁屏壁纸

怎样更换电脑锁屏壁纸?

windows聚焦锁屏壁纸不显示

iOS锁屏超实用功能化!设置教程

Android6.0锁屏源码分析之界面布局分析

将未激活Win10锁屏壁纸设为桌面壁纸