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.os.Bundle;
20 import android.os.Message;
21 import android.os.RemoteException;
22 import android.util.Log;
23 import android.view.Surface;
24 import android.view.SurfaceHolder;
25 import android.view.SurfaceView;
26 
27 import androidx.annotation.Nullable;
28 
29 import com.android.wallpaper.util.PreviewUtils;
30 import com.android.wallpaper.util.SurfaceViewUtils;
31 
32 import java.util.concurrent.atomic.AtomicBoolean;
33 
34 /** A surface holder callback that renders user's workspace on the passed in surface view. */
35 public class WorkspaceSurfaceHolderCallback implements SurfaceHolder.Callback {
36 
37     /**
38      * Listener to be called when workspace surface is updated with a new Surface Package.
39      */
40     public interface WorkspaceRenderListener {
41         /**
42          * Called on the main thread after the workspace surface is updated from the provider
43          */
onWorkspaceRendered()44         void onWorkspaceRendered();
45     }
46 
47     private static final String TAG = "WsSurfaceHolderCallback";
48     private static final String KEY_WALLPAPER_COLORS = "wallpaper_colors";
49     public static final int MESSAGE_ID_UPDATE_PREVIEW = 1337;
50     public static final String KEY_HIDE_BOTTOM_ROW = "hide_bottom_row";
51     public static final int MESSAGE_ID_COLOR_OVERRIDE = 1234;
52     public static final String KEY_COLOR_OVERRIDE = "color_override"; // ColorInt Encoded as string
53     private final SurfaceView mWorkspaceSurface;
54     private final PreviewUtils mPreviewUtils;
55     private final boolean mShouldUseWallpaperColors;
56     private final AtomicBoolean mRequestPending = new AtomicBoolean(false);
57 
58     private WallpaperColors mWallpaperColors;
59     private boolean mHideBottomRow;
60     private boolean mIsWallpaperColorsReady;
61     private Surface mLastSurface;
62     private Message mCallback;
63     private Message mDelayedMessage;
64     private WorkspaceRenderListener mListener;
65 
66     private boolean mNeedsToCleanUp;
67     @Nullable private final Bundle mExtras;
68 
69     private int mWidth = -1;
70 
71     private int mHeight = -1;
72 
WorkspaceSurfaceHolderCallback( SurfaceView workspaceSurface, PreviewUtils previewUtils)73     public WorkspaceSurfaceHolderCallback(
74             SurfaceView workspaceSurface,
75             PreviewUtils previewUtils) {
76         this(workspaceSurface, previewUtils, false, null);
77     }
78 
79     /**
80      * Creates a new instance of {@link WorkspaceSurfaceHolderCallback} specifying if wallpaper
81      * colors should be used to preview the workspace.
82      *
83      * @param shouldUseWallpaperColors if true, the workspace preview won't be requested until both
84      *                                 the surface is created and wallpaper colors are set via
85      *                                 {@link #setWallpaperColors(WallpaperColors)}
86      */
WorkspaceSurfaceHolderCallback( SurfaceView workspaceSurface, PreviewUtils previewUtils, boolean shouldUseWallpaperColors)87     public WorkspaceSurfaceHolderCallback(
88             SurfaceView workspaceSurface,
89             PreviewUtils previewUtils,
90             boolean shouldUseWallpaperColors) {
91         this(
92                 workspaceSurface,
93                 previewUtils,
94                 shouldUseWallpaperColors,
95                 null);
96     }
97 
WorkspaceSurfaceHolderCallback( SurfaceView workspaceSurface, PreviewUtils previewUtils, @Nullable Bundle extras)98     public WorkspaceSurfaceHolderCallback(
99             SurfaceView workspaceSurface,
100             PreviewUtils previewUtils,
101             @Nullable Bundle extras) {
102         this(workspaceSurface, previewUtils, false, extras);
103     }
104 
WorkspaceSurfaceHolderCallback( SurfaceView workspaceSurface, PreviewUtils previewUtils, boolean shouldUseWallpaperColors, @Nullable Bundle extras)105     private WorkspaceSurfaceHolderCallback(
106             SurfaceView workspaceSurface,
107             PreviewUtils previewUtils,
108             boolean shouldUseWallpaperColors,
109             @Nullable Bundle extras) {
110         mWorkspaceSurface = workspaceSurface;
111         mPreviewUtils = previewUtils;
112         mShouldUseWallpaperColors = shouldUseWallpaperColors;
113         mExtras = extras;
114     }
115 
116     @Override
surfaceCreated(SurfaceHolder holder)117     public void surfaceCreated(SurfaceHolder holder) {
118         if (mPreviewUtils.supportsPreview() && mLastSurface != holder.getSurface()) {
119             mLastSurface = holder.getSurface();
120             maybeRenderPreview();
121         }
122     }
123 
124     /**
125      * Set the current wallpaper's colors. This method must be called  if this instance was created
126      * with shouldUseWallpaperColors = true (even with {@code null} colors), and conversely, calling
127      * this method when {@code shouldUseWallpaperColors = false} will be a no-op.
128      *
129      * @param colors WallpaperColors extracted from the current wallpaper preview, or {@code null}
130      *               if none are available.
131      * @see #WorkspaceSurfaceHolderCallback(SurfaceView, PreviewUtils, boolean)
132      */
setWallpaperColors(@ullable WallpaperColors colors)133     public void setWallpaperColors(@Nullable WallpaperColors colors) {
134         if (!mShouldUseWallpaperColors) {
135             return;
136         }
137         mWallpaperColors = colors;
138         mIsWallpaperColorsReady = true;
139     }
140 
141     /**
142      * Set the current flag if we should hide the workspace bottom row.
143      */
setHideBottomRow(boolean hideBottomRow)144     public void setHideBottomRow(boolean hideBottomRow) {
145         mHideBottomRow = hideBottomRow;
146     }
147 
setListener(WorkspaceRenderListener listener)148     public void setListener(WorkspaceRenderListener listener) {
149         mListener = listener;
150     }
151 
152     /**
153      * Render the preview with the current selected {@link #mWallpaperColors} and
154      * {@link #mHideBottomRow}.
155      */
maybeRenderPreview()156     public void maybeRenderPreview() {
157         if ((mShouldUseWallpaperColors && !mIsWallpaperColorsReady) || mLastSurface == null) {
158             return;
159         }
160         mRequestPending.set(true);
161         requestPreview(mWorkspaceSurface, (result) -> {
162             mRequestPending.set(false);
163             if (result != null && mLastSurface != null) {
164                 mWorkspaceSurface.setChildSurfacePackage(
165                         SurfaceViewUtils.getSurfacePackage(result));
166                 mCallback = SurfaceViewUtils.getCallback(result);
167                 if (mCallback != null && mDelayedMessage != null) {
168                     try {
169                         mCallback.replyTo.send(mDelayedMessage);
170                     } catch (RemoteException e) {
171                         Log.w(TAG, "Couldn't send message to workspace preview", e);
172                     }
173                     mDelayedMessage = null;
174                 }
175                 if (mNeedsToCleanUp) {
176                     cleanUp();
177                 } else if (mListener != null) {
178                     mListener.onWorkspaceRendered();
179                 }
180             }
181         });
182     }
183 
184     @Override
surfaceChanged(SurfaceHolder holder, int format, int width, int height)185     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
186         if ((mWidth != -1 || mHeight != -1) && (mWidth != width || mHeight != height)) {
187             maybeRenderPreview();
188         }
189         mWidth = width;
190         mHeight = height;
191     }
192 
193     @Override
surfaceDestroyed(SurfaceHolder holder)194     public void surfaceDestroyed(SurfaceHolder holder) {
195     }
196 
197     /**
198      * Sends a message to the remote renderer.
199      *
200      * @param what An ID for the message (the remote side can pick this up through
201      * {@link Message#what}.
202      * @param bundle The data of the message (the remote side can pick this up through
203      * {@link Message#getData()}.
204      */
send(final int what, @Nullable Bundle bundle)205     public void send(final int what, @Nullable Bundle bundle) {
206         final Message message = new Message();
207         message.what = what;
208         message.setData(bundle);
209         if (mCallback != null) {
210             try {
211                 mCallback.replyTo.send(message);
212             } catch (RemoteException e) {
213                 Log.w(TAG, "Couldn't send message to workspace preview", e);
214             }
215         } else {
216             mDelayedMessage = message;
217         }
218     }
219 
cleanUp()220     public void cleanUp() {
221         if (mCallback != null) {
222             try {
223                 mCallback.replyTo.send(mCallback);
224                 mNeedsToCleanUp = false;
225             } catch (RemoteException e) {
226                 Log.w(TAG, "Couldn't call cleanup on workspace preview", e);
227             } finally {
228                 mCallback = null;
229             }
230         } else {
231             if (mRequestPending.get()) {
232                 mNeedsToCleanUp = true;
233             }
234         }
235     }
236 
resetLastSurface()237     public void resetLastSurface() {
238         mLastSurface = null;
239     }
240 
requestPreview(SurfaceView workspaceSurface, PreviewUtils.WorkspacePreviewCallback callback)241     protected void requestPreview(SurfaceView workspaceSurface,
242             PreviewUtils.WorkspacePreviewCallback callback) {
243         if (workspaceSurface.getDisplay() == null) {
244             Log.w(TAG,
245                     "No display ID, avoiding asking for workspace preview, lest WallpaperPicker "
246                             + "crash");
247             return;
248         }
249         Bundle request = SurfaceViewUtils.createSurfaceViewRequest(workspaceSurface, mExtras);
250         if (mWallpaperColors != null) {
251             request.putParcelable(KEY_WALLPAPER_COLORS, mWallpaperColors);
252         }
253         request.putBoolean(KEY_HIDE_BOTTOM_ROW, mHideBottomRow);
254         mPreviewUtils.renderPreview(request, callback);
255     }
256 }
257