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