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 79 private int mWidth = -1; 80 81 private int mHeight = -1; 82 WallpaperSurfaceCallback(Context context, View containerView, SurfaceView wallpaperSurface, @Nullable Future<ColorInfo> colorFuture, @Nullable SurfaceListener listener)83 public WallpaperSurfaceCallback(Context context, View containerView, 84 SurfaceView wallpaperSurface, @Nullable Future<ColorInfo> colorFuture, 85 @Nullable SurfaceListener listener) { 86 mContext = context.getApplicationContext(); 87 mContainerView = containerView; 88 mWallpaperSurface = wallpaperSurface; 89 mListener = listener; 90 91 // Notify WallpaperSurface to reset image wallpaper when encountered live wallpaper's 92 // package been changed in background. 93 Injector injector = InjectorProvider.getInjector(); 94 mPackageStatusNotifier = injector.getPackageStatusNotifier(context); 95 mAppStatusListener = (packageName, status) -> { 96 if (status != PackageStatusNotifier.PackageStatus.REMOVED) { 97 resetHomeImageWallpaper(); 98 } 99 }; 100 mPackageStatusNotifier.addListener(mAppStatusListener, 101 WallpaperService.SERVICE_INTERFACE); 102 mColorFuture = colorFuture; 103 } 104 WallpaperSurfaceCallback(Context context, View containerView, SurfaceView wallpaperSurface, @Nullable SurfaceListener listener)105 public WallpaperSurfaceCallback(Context context, View containerView, 106 SurfaceView wallpaperSurface, @Nullable SurfaceListener listener) { 107 this(context, containerView, wallpaperSurface, /* colorFuture= */ null, listener); 108 } 109 WallpaperSurfaceCallback(Context context, View containerView, SurfaceView wallpaperSurface)110 public WallpaperSurfaceCallback(Context context, View containerView, 111 SurfaceView wallpaperSurface) { 112 this(context, containerView, wallpaperSurface, null); 113 } 114 115 @Override surfaceCreated(SurfaceHolder holder)116 public void surfaceCreated(SurfaceHolder holder) { 117 if (mLastSurface != holder.getSurface()) { 118 mLastSurface = holder.getSurface(); 119 setupSurfaceWallpaper(/* forceClean= */ true); 120 } 121 if (mListener != null) { 122 mListener.onSurfaceCreated(); 123 } 124 if (mHost != null && mHost.getView() != null) { 125 mWidth = mHost.getView().getWidth(); 126 mHeight = mHost.getView().getHeight(); 127 } 128 mSurfaceCreated = true; 129 } 130 131 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)132 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 133 if ((mWidth != -1 || mHeight != -1) && (mWidth != width || mHeight != height)) { 134 resizeSurfaceWallpaper(); 135 } 136 mWidth = width; 137 mHeight = height; 138 } 139 140 @Override surfaceDestroyed(SurfaceHolder holder)141 public void surfaceDestroyed(SurfaceHolder holder) { 142 mSurfaceCreated = false; 143 } 144 145 /** 146 * Call to release resources and app status listener. 147 */ cleanUp()148 public void cleanUp() { 149 releaseHost(); 150 if (mHomeImageWallpaper != null) { 151 mHomeImageWallpaper.setImageDrawable(null); 152 } 153 mPackageStatusNotifier.removeListener(mAppStatusListener); 154 if (mWallpaperSurface.getSurfaceControl() != null) { 155 mWallpaperSurface.getSurfaceControl().release(); 156 } 157 } 158 releaseHost()159 private void releaseHost() { 160 if (mHost != null) { 161 mHost.release(); 162 mHost = null; 163 } 164 } 165 166 /** 167 * Reset existing image wallpaper by creating a new ImageView for SurfaceControlViewHost 168 * if surface state is not created. 169 */ resetHomeImageWallpaper()170 private void resetHomeImageWallpaper() { 171 if (mSurfaceCreated) { 172 return; 173 } 174 175 if (mHost != null) { 176 setupSurfaceWallpaper(/* forceClean= */ false); 177 } 178 } 179 setupSurfaceWallpaper(boolean forceClean)180 private void setupSurfaceWallpaper(boolean forceClean) { 181 mHomeImageWallpaper = new ImageView(mContext); 182 Integer placeholder = null; 183 if (mColorFuture != null && mColorFuture.isDone()) { 184 try { 185 ColorInfo colorInfo = mColorFuture.get(); 186 if (colorInfo != null) { 187 placeholder = colorInfo.getPlaceholderColor(); 188 } 189 } catch (InterruptedException | ExecutionException e) { 190 Log.e(TAG, "Couldn't get placeholder from ColorInfo."); 191 } 192 } 193 int bkgColor = (placeholder != null) ? placeholder 194 : ResourceUtils.getColorAttr(mContext, android.R.attr.colorSecondary); 195 mHomeImageWallpaper.setBackgroundColor(bkgColor); 196 mWallpaperSurface.setBackgroundColor(bkgColor); 197 mHomeImageWallpaper.measure(makeMeasureSpec(mContainerView.getWidth(), EXACTLY), 198 makeMeasureSpec(mContainerView.getHeight(), EXACTLY)); 199 mHomeImageWallpaper.layout(0, 0, mContainerView.getWidth(), 200 mContainerView.getHeight()); 201 if (forceClean) { 202 releaseHost(); 203 mHost = new SurfaceControlViewHost(mContext, 204 mContainerView.getDisplay(), mWallpaperSurface.getHostToken()); 205 } 206 mHost.setView(mHomeImageWallpaper, mHomeImageWallpaper.getWidth(), 207 mHomeImageWallpaper.getHeight()); 208 mWallpaperSurface.setChildSurfacePackage(mHost.getSurfacePackage()); 209 } 210 resizeSurfaceWallpaper()211 private void resizeSurfaceWallpaper() { 212 mHomeImageWallpaper.measure(makeMeasureSpec(mContainerView.getWidth(), EXACTLY), 213 makeMeasureSpec(mContainerView.getHeight(), EXACTLY)); 214 mHomeImageWallpaper.layout(0, 0, mContainerView.getWidth(), 215 mContainerView.getHeight()); 216 mHost.relayout(mHomeImageWallpaper.getWidth(), mHomeImageWallpaper.getHeight()); 217 } 218 219 @Nullable getHomeImageWallpaper()220 public ImageView getHomeImageWallpaper() { 221 return mHomeImageWallpaper; 222 } 223 224 /** 225 * @param blur whether to blur the home image wallpaper 226 */ setHomeImageWallpaperBlur(boolean blur)227 public void setHomeImageWallpaperBlur(boolean blur) { 228 if (mHomeImageWallpaper == null) { 229 return; 230 } 231 if (blur) { 232 mHomeImageWallpaper.setRenderEffect( 233 RenderEffect.createBlurEffect(LOW_RES_BITMAP_BLUR_RADIUS, 234 LOW_RES_BITMAP_BLUR_RADIUS, Shader.TileMode.CLAMP)); 235 } else { 236 mHomeImageWallpaper.setRenderEffect(null); 237 } 238 } 239 } 240