1 /* 2 * Copyright (C) 2020 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 package com.android.wallpaper.util; 17 18 import static android.view.View.MeasureSpec.EXACTLY; 19 import static android.view.View.MeasureSpec.makeMeasureSpec; 20 21 import android.content.Context; 22 import android.graphics.RenderEffect; 23 import android.graphics.Shader; 24 import android.service.wallpaper.WallpaperService; 25 import android.util.Log; 26 import android.view.Surface; 27 import android.view.SurfaceControlViewHost; 28 import android.view.SurfaceHolder; 29 import android.view.SurfaceView; 30 import android.view.View; 31 import android.widget.ImageView; 32 33 import androidx.annotation.Nullable; 34 35 import com.android.wallpaper.model.WallpaperInfo.ColorInfo; 36 import com.android.wallpaper.module.Injector; 37 import com.android.wallpaper.module.InjectorProvider; 38 import com.android.wallpaper.module.PackageStatusNotifier; 39 40 import java.util.concurrent.ExecutionException; 41 import java.util.concurrent.Future; 42 43 /** 44 * Default implementation of {@link SurfaceHolder.Callback} to render a static wallpaper when the 45 * surface has been created. 46 */ 47 public class WallpaperSurfaceCallback implements SurfaceHolder.Callback { 48 49 public static final float LOW_RES_BITMAP_BLUR_RADIUS = 150f; 50 51 /** 52 * Listener used to be notified when this surface is created 53 */ 54 public interface SurfaceListener { 55 /** 56 * Called when {@link WallpaperSurfaceCallback#surfaceCreated(SurfaceHolder)} is called. 57 */ onSurfaceCreated()58 void onSurfaceCreated(); 59 } 60 61 private static final String TAG = "WallpaperSurfaceCallback"; 62 private Surface mLastSurface; 63 private SurfaceControlViewHost mHost; 64 // Home workspace surface is behind the app window, and so must the home image wallpaper like 65 // the live wallpaper. This view is rendered on here for home image wallpaper. 66 private ImageView mHomeImageWallpaper; 67 private final Context mContext; 68 private final View mContainerView; 69 private final SurfaceView mWallpaperSurface; 70 @Nullable 71 private final SurfaceListener mListener; 72 @Nullable 73 private final Future<ColorInfo> mColorFuture; 74 private boolean mSurfaceCreated; 75 76 private PackageStatusNotifier.Listener mAppStatusListener; 77 private PackageStatusNotifier mPackageStatusNotifier; 78 WallpaperSurfaceCallback(Context context, View containerView, SurfaceView wallpaperSurface, @Nullable Future<ColorInfo> colorFuture, @Nullable SurfaceListener listener)79 public WallpaperSurfaceCallback(Context context, View containerView, 80 SurfaceView wallpaperSurface, @Nullable Future<ColorInfo> colorFuture, 81 @Nullable SurfaceListener listener) { 82 mContext = context.getApplicationContext(); 83 mContainerView = containerView; 84 mWallpaperSurface = wallpaperSurface; 85 mListener = listener; 86 87 // Notify WallpaperSurface to reset image wallpaper when encountered live wallpaper's 88 // package been changed in background. 89 Injector injector = InjectorProvider.getInjector(); 90 mPackageStatusNotifier = injector.getPackageStatusNotifier(context); 91 mAppStatusListener = (packageName, status) -> { 92 if (status != PackageStatusNotifier.PackageStatus.REMOVED) { 93 resetHomeImageWallpaper(); 94 } 95 }; 96 mPackageStatusNotifier.addListener(mAppStatusListener, 97 WallpaperService.SERVICE_INTERFACE); 98 mColorFuture = colorFuture; 99 } 100 WallpaperSurfaceCallback(Context context, View containerView, SurfaceView wallpaperSurface, @Nullable SurfaceListener listener)101 public WallpaperSurfaceCallback(Context context, View containerView, 102 SurfaceView wallpaperSurface, @Nullable SurfaceListener listener) { 103 this(context, containerView, wallpaperSurface, /* colorFuture= */ null, listener); 104 } 105 WallpaperSurfaceCallback(Context context, View containerView, SurfaceView wallpaperSurface)106 public WallpaperSurfaceCallback(Context context, View containerView, 107 SurfaceView wallpaperSurface) { 108 this(context, containerView, wallpaperSurface, null); 109 } 110 111 @Override surfaceCreated(SurfaceHolder holder)112 public void surfaceCreated(SurfaceHolder holder) { 113 if (mLastSurface != holder.getSurface()) { 114 mLastSurface = holder.getSurface(); 115 setupSurfaceWallpaper(/* forceClean= */ true); 116 } 117 if (mListener != null) { 118 mListener.onSurfaceCreated(); 119 } 120 mSurfaceCreated = true; 121 } 122 123 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)124 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } 125 126 @Override surfaceDestroyed(SurfaceHolder holder)127 public void surfaceDestroyed(SurfaceHolder holder) { 128 mSurfaceCreated = false; 129 } 130 131 /** 132 * Call to release resources and app status listener. 133 */ cleanUp()134 public void cleanUp() { 135 releaseHost(); 136 if (mHomeImageWallpaper != null) { 137 mHomeImageWallpaper.setImageDrawable(null); 138 } 139 mPackageStatusNotifier.removeListener(mAppStatusListener); 140 } 141 releaseHost()142 private void releaseHost() { 143 if (mHost != null) { 144 mHost.release(); 145 mHost = null; 146 } 147 } 148 149 /** 150 * Reset existing image wallpaper by creating a new ImageView for SurfaceControlViewHost 151 * if surface state is not created. 152 */ resetHomeImageWallpaper()153 private void resetHomeImageWallpaper() { 154 if (mSurfaceCreated) { 155 return; 156 } 157 158 if (mHost != null) { 159 setupSurfaceWallpaper(/* forceClean= */ false); 160 } 161 } 162 setupSurfaceWallpaper(boolean forceClean)163 private void setupSurfaceWallpaper(boolean forceClean) { 164 mHomeImageWallpaper = new ImageView(mContext); 165 Integer placeholder = null; 166 if (mColorFuture != null && mColorFuture.isDone()) { 167 try { 168 ColorInfo colorInfo = mColorFuture.get(); 169 if (colorInfo != null) { 170 placeholder = colorInfo.getPlaceholderColor(); 171 } 172 } catch (InterruptedException | ExecutionException e) { 173 Log.e(TAG, "Couldn't get placeholder from ColorInfo."); 174 } 175 } 176 mHomeImageWallpaper.setBackgroundColor((placeholder != null) ? placeholder 177 : ResourceUtils.getColorAttr(mContext, android.R.attr.colorSecondary)); 178 mHomeImageWallpaper.measure(makeMeasureSpec(mContainerView.getWidth(), EXACTLY), 179 makeMeasureSpec(mContainerView.getHeight(), EXACTLY)); 180 mHomeImageWallpaper.layout(0, 0, mContainerView.getWidth(), 181 mContainerView.getHeight()); 182 if (forceClean) { 183 releaseHost(); 184 mHost = new SurfaceControlViewHost(mContext, 185 mContainerView.getDisplay(), mWallpaperSurface.getHostToken()); 186 } 187 mHost.setView(mHomeImageWallpaper, mHomeImageWallpaper.getWidth(), 188 mHomeImageWallpaper.getHeight()); 189 mWallpaperSurface.setChildSurfacePackage(mHost.getSurfacePackage()); 190 } 191 192 @Nullable getHomeImageWallpaper()193 public ImageView getHomeImageWallpaper() { 194 return mHomeImageWallpaper; 195 } 196 197 /** 198 * @param blur whether to blur the home image wallpaper 199 */ setHomeImageWallpaperBlur(boolean blur)200 public void setHomeImageWallpaperBlur(boolean blur) { 201 if (mHomeImageWallpaper == null) { 202 return; 203 } 204 if (blur) { 205 mHomeImageWallpaper.setRenderEffect( 206 RenderEffect.createBlurEffect(LOW_RES_BITMAP_BLUR_RADIUS, 207 LOW_RES_BITMAP_BLUR_RADIUS, Shader.TileMode.CLAMP)); 208 } else { 209 mHomeImageWallpaper.setRenderEffect(null); 210 } 211 } 212 } 213