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