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.service.wallpaper.WallpaperService;
23 import android.view.Surface;
24 import android.view.SurfaceControlViewHost;
25 import android.view.SurfaceHolder;
26 import android.view.SurfaceView;
27 import android.view.View;
28 import android.widget.ImageView;
29 
30 import androidx.annotation.Nullable;
31 
32 import com.android.wallpaper.module.Injector;
33 import com.android.wallpaper.module.InjectorProvider;
34 import com.android.wallpaper.module.PackageStatusNotifier;
35 
36 import java.util.concurrent.ExecutionException;
37 import java.util.concurrent.Future;
38 
39 /**
40  * Default implementation of {@link SurfaceHolder.Callback} to render a static wallpaper when the
41  * surface has been created.
42  */
43 public class WallpaperSurfaceCallback implements SurfaceHolder.Callback {
44 
45     /**
46      * Listener used to be notified when this surface is created
47      */
48     public interface SurfaceListener {
49         /**
50          * Called when {@link WallpaperSurfaceCallback#surfaceCreated(SurfaceHolder)} is called.
51          */
onSurfaceCreated()52         void onSurfaceCreated();
53     }
54 
55     private Surface mLastSurface;
56     private SurfaceControlViewHost mHost;
57     // Home workspace surface is behind the app window, and so must the home image wallpaper like
58     // the live wallpaper. This view is rendered on here for home image wallpaper.
59     private ImageView mHomeImageWallpaper;
60     private final Context mContext;
61     private final View mContainerView;
62     private final SurfaceView mWallpaperSurface;
63     @Nullable
64     private final SurfaceListener mListener;
65     @Nullable
66     private final Future<Integer> mPlaceholderColor;
67     private boolean mSurfaceCreated;
68 
69     private PackageStatusNotifier.Listener mAppStatusListener;
70     private PackageStatusNotifier mPackageStatusNotifier;
71 
WallpaperSurfaceCallback(Context context, View containerView, SurfaceView wallpaperSurface, @Nullable Future<Integer> placeholderColor, @Nullable SurfaceListener listener)72     public WallpaperSurfaceCallback(Context context, View containerView,
73             SurfaceView wallpaperSurface, @Nullable Future<Integer> placeholderColor,
74             @Nullable SurfaceListener listener) {
75         mContext = context.getApplicationContext();
76         mContainerView = containerView;
77         mWallpaperSurface = wallpaperSurface;
78         mListener = listener;
79 
80         // Notify WallpaperSurface to reset image wallpaper when encountered live wallpaper's
81         // package been changed in background.
82         Injector injector = InjectorProvider.getInjector();
83         mPackageStatusNotifier = injector.getPackageStatusNotifier(context);
84         mAppStatusListener = (packageName, status) -> {
85             if (status != PackageStatusNotifier.PackageStatus.REMOVED) {
86                 resetHomeImageWallpaper();
87             }
88         };
89         mPackageStatusNotifier.addListener(mAppStatusListener,
90                 WallpaperService.SERVICE_INTERFACE);
91         mPlaceholderColor = placeholderColor;
92     }
93 
WallpaperSurfaceCallback(Context context, View containerView, SurfaceView wallpaperSurface, @Nullable SurfaceListener listener)94     public WallpaperSurfaceCallback(Context context, View containerView,
95             SurfaceView wallpaperSurface, @Nullable SurfaceListener listener) {
96         this(context, containerView, wallpaperSurface, null, listener);
97     }
98 
WallpaperSurfaceCallback(Context context, View containerView, SurfaceView wallpaperSurface)99     public WallpaperSurfaceCallback(Context context, View containerView,
100             SurfaceView wallpaperSurface) {
101         this(context, containerView, wallpaperSurface, null);
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     @Override
surfaceDestroyed(SurfaceHolder holder)120     public void surfaceDestroyed(SurfaceHolder holder) {
121         mSurfaceCreated = false;
122     }
123 
124     /**
125      * Call to release resources and app status listener.
126      */
cleanUp()127     public void cleanUp() {
128         releaseHost();
129         if (mHomeImageWallpaper != null) {
130             mHomeImageWallpaper.setImageDrawable(null);
131         }
132         mPackageStatusNotifier.removeListener(mAppStatusListener);
133     }
134 
releaseHost()135     private void releaseHost() {
136         if (mHost != null) {
137             mHost.release();
138             mHost = null;
139         }
140     }
141 
142     /**
143      * Reset existing image wallpaper by creating a new ImageView for SurfaceControlViewHost
144      * if surface state is not created.
145      */
resetHomeImageWallpaper()146     private void resetHomeImageWallpaper() {
147         if (mSurfaceCreated) {
148             return;
149         }
150 
151         if (mHost != null) {
152             setupSurfaceWallpaper(/* forceClean= */ false);
153         }
154     }
155 
setupSurfaceWallpaper(boolean forceClean)156     private void setupSurfaceWallpaper(boolean forceClean) {
157         mHomeImageWallpaper = new ImageView(mContext);
158         Integer placeholder = null;
159         if (mPlaceholderColor != null && mPlaceholderColor.isDone()) {
160             try {
161                 placeholder = mPlaceholderColor.get();
162             } catch (InterruptedException | ExecutionException e) {
163                 // Ignore
164             }
165         }
166         mHomeImageWallpaper.setBackgroundColor((placeholder != null) ? placeholder
167                 : ResourceUtils.getColorAttr(mContext, android.R.attr.colorSecondary));
168         mHomeImageWallpaper.measure(makeMeasureSpec(mContainerView.getWidth(), EXACTLY),
169                 makeMeasureSpec(mContainerView.getHeight(), EXACTLY));
170         mHomeImageWallpaper.layout(0, 0, mContainerView.getWidth(),
171                 mContainerView.getHeight());
172         if (forceClean) {
173             releaseHost();
174             mHost = new SurfaceControlViewHost(mContext,
175                     mContainerView.getDisplay(), mWallpaperSurface.getHostToken());
176         }
177         mHost.setView(mHomeImageWallpaper, mHomeImageWallpaper.getWidth(),
178                 mHomeImageWallpaper.getHeight());
179         mWallpaperSurface.setChildSurfacePackage(mHost.getSurfacePackage());
180     }
181 
182     @Nullable
getHomeImageWallpaper()183     public ImageView getHomeImageWallpaper() {
184         return mHomeImageWallpaper;
185     }
186 }
187