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