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.picker;
17 
18 import android.app.WallpaperColors;
19 import android.content.Context;
20 import android.os.Bundle;
21 import android.os.Message;
22 import android.os.RemoteException;
23 import android.util.Log;
24 import android.view.Surface;
25 import android.view.SurfaceHolder;
26 import android.view.SurfaceView;
27 
28 import androidx.annotation.Nullable;
29 
30 import com.android.wallpaper.R;
31 import com.android.wallpaper.util.PreviewUtils;
32 import com.android.wallpaper.util.SurfaceViewUtils;
33 
34 import java.util.concurrent.atomic.AtomicBoolean;
35 
36 /** A surface holder callback that renders user's workspace on the passed in surface view. */
37 public class WorkspaceSurfaceHolderCallback implements SurfaceHolder.Callback {
38 
39     /**
40      * Listener to be called when workspace surface is updated with a new Surface Package.
41      */
42     public interface WorkspaceRenderListener {
43         /**
44          * Called on the main thread after the workspace surface is updated from the provider
45          */
onWorkspaceRendered()46         void onWorkspaceRendered();
47     }
48 
49     private static final String TAG = "WsSurfaceHolderCallback";
50     private static final String KEY_WALLPAPER_COLORS = "wallpaper_colors";
51     private final SurfaceView mWorkspaceSurface;
52     private final PreviewUtils mPreviewUtils;
53     private final boolean mShouldUseWallpaperColors;
54     private final AtomicBoolean mRequestPending = new AtomicBoolean(false);
55 
56     private WallpaperColors mWallpaperColors;
57     private boolean mIsWallpaperColorsReady;
58     private Surface mLastSurface;
59     private Message mCallback;
60     private WorkspaceRenderListener mListener;
61 
62     private boolean mNeedsToCleanUp;
63 
WorkspaceSurfaceHolderCallback(SurfaceView workspaceSurface, Context context)64     public WorkspaceSurfaceHolderCallback(SurfaceView workspaceSurface, Context context) {
65         this(workspaceSurface, context, false);
66     }
67 
68     /**
69      * Creates a new instance of {@link WorkspaceSurfaceHolderCallback} specifying if wallpaper
70      * colors should be used to preview the workspace.
71      *
72      * @param shouldUseWallpaperColors if true, the workspace preview won't be requested until both
73      *                                 the surface is created and wallpaper colors are set via
74      *                                 {@link #setWallpaperColors(WallpaperColors)}
75      */
WorkspaceSurfaceHolderCallback(SurfaceView workspaceSurface, Context context, boolean shouldUseWallpaperColors)76     public WorkspaceSurfaceHolderCallback(SurfaceView workspaceSurface, Context context,
77             boolean shouldUseWallpaperColors) {
78         mWorkspaceSurface = workspaceSurface;
79         mPreviewUtils = new PreviewUtils(context,
80                 context.getString(R.string.grid_control_metadata_name));
81         mShouldUseWallpaperColors = shouldUseWallpaperColors;
82     }
83 
84     @Override
surfaceCreated(SurfaceHolder holder)85     public void surfaceCreated(SurfaceHolder holder) {
86         if (mPreviewUtils.supportsPreview() && mLastSurface != holder.getSurface()) {
87             mLastSurface = holder.getSurface();
88             maybeRenderPreview();
89         }
90     }
91 
92     /**
93      * Set the current wallpaper's colors. This method must be called  if this instance was created
94      * with shouldUseWallpaperColors = true (even with {@code null} colors), and conversely, calling
95      * this method when {@code shouldUseWallpaperColors = false} will be a no-op.
96      *
97      * @param colors WallpaperColors extracted from the current wallpaper preview, or {@code null}
98      *               if none are available.
99      * @see #WorkspaceSurfaceHolderCallback(SurfaceView, Context, boolean)
100      */
setWallpaperColors(@ullable WallpaperColors colors)101     public void setWallpaperColors(@Nullable WallpaperColors colors) {
102         if (!mShouldUseWallpaperColors) {
103             return;
104         }
105         mWallpaperColors = colors;
106         mIsWallpaperColorsReady = true;
107         maybeRenderPreview();
108     }
109 
setListener(WorkspaceRenderListener listener)110     public void setListener(WorkspaceRenderListener listener) {
111         mListener = listener;
112     }
113 
maybeRenderPreview()114     private void maybeRenderPreview() {
115         if ((mShouldUseWallpaperColors && !mIsWallpaperColorsReady) || mLastSurface == null) {
116             return;
117         }
118 
119         mRequestPending.set(true);
120         requestPreview(mWorkspaceSurface, (result) -> {
121             mRequestPending.set(false);
122             if (result != null && mLastSurface != null) {
123                 mWorkspaceSurface.setChildSurfacePackage(
124                         SurfaceViewUtils.getSurfacePackage(result));
125 
126                 mCallback = SurfaceViewUtils.getCallback(result);
127 
128                 if (mNeedsToCleanUp) {
129                     cleanUp();
130                 } else if (mListener != null) {
131                     mListener.onWorkspaceRendered();
132                 }
133             }
134         });
135     }
136 
137     @Override
surfaceChanged(SurfaceHolder holder, int format, int width, int height)138     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }
139 
140     @Override
surfaceDestroyed(SurfaceHolder holder)141     public void surfaceDestroyed(SurfaceHolder holder) {
142     }
143 
cleanUp()144     public void cleanUp() {
145         if (mCallback != null) {
146             try {
147                 mCallback.replyTo.send(mCallback);
148                 mNeedsToCleanUp = false;
149             } catch (RemoteException e) {
150                 Log.w(TAG, "Couldn't call cleanup on workspace preview", e);
151             } finally {
152                 mCallback = null;
153             }
154         } else {
155             if (mRequestPending.get()) {
156                 mNeedsToCleanUp = true;
157             }
158         }
159     }
160 
resetLastSurface()161     public void resetLastSurface() {
162         mLastSurface = null;
163     }
164 
requestPreview(SurfaceView workspaceSurface, PreviewUtils.WorkspacePreviewCallback callback)165     protected void requestPreview(SurfaceView workspaceSurface,
166             PreviewUtils.WorkspacePreviewCallback callback) {
167         if (workspaceSurface.getDisplay() == null) {
168             Log.w(TAG,
169                     "No display ID, avoiding asking for workspace preview, lest WallpaperPicker "
170                             + "crash");
171             return;
172         }
173         Bundle request = SurfaceViewUtils.createSurfaceViewRequest(workspaceSurface);
174         if (mWallpaperColors != null) {
175             request.putParcelable(KEY_WALLPAPER_COLORS, mWallpaperColors);
176         }
177         mPreviewUtils.renderPreview(request, callback);
178     }
179 }
180