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.graphics.Matrix.MSCALE_X;
19 import static android.graphics.Matrix.MSCALE_Y;
20 import static android.graphics.Matrix.MSKEW_X;
21 import static android.graphics.Matrix.MSKEW_Y;
22 
23 import android.app.WallpaperColors;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.ServiceConnection;
28 import android.graphics.Matrix;
29 import android.graphics.Rect;
30 import android.graphics.RectF;
31 import android.os.IBinder;
32 import android.os.ParcelFileDescriptor;
33 import android.os.RemoteException;
34 import android.service.wallpaper.IWallpaperConnection;
35 import android.service.wallpaper.IWallpaperEngine;
36 import android.service.wallpaper.IWallpaperService;
37 import android.util.DisplayMetrics;
38 import android.util.Log;
39 import android.view.SurfaceControl;
40 import android.view.SurfaceHolder;
41 import android.view.SurfaceHolder.Callback;
42 import android.view.SurfaceView;
43 import android.view.WindowManager.LayoutParams;
44 
45 import androidx.annotation.Nullable;
46 
47 /**
48  * Implementation of {@link IWallpaperConnection} that handles communication with a
49  * {@link android.service.wallpaper.WallpaperService}
50  */
51 public class WallpaperConnection extends IWallpaperConnection.Stub implements ServiceConnection {
52 
53     /**
54      * Returns whether live preview is available in framework.
55      */
isPreviewAvailable()56     public static boolean isPreviewAvailable() {
57         try {
58             return IWallpaperEngine.class.getMethod("mirrorSurfaceControl") != null;
59         } catch (NoSuchMethodException | SecurityException e) {
60             return false;
61         }
62     }
63 
64     private static final String TAG = "WallpaperConnection";
65     private final Context mContext;
66     private final Intent mIntent;
67     private final WallpaperConnectionListener mListener;
68     private final SurfaceView mContainerView;
69     private final SurfaceView mSecondContainerView;
70     private IWallpaperService mService;
71     @Nullable private IWallpaperEngine mEngine;
72     private boolean mConnected;
73     private boolean mIsVisible;
74     private boolean mIsEngineVisible;
75     private boolean mEngineReady;
76 
77     /**
78      * @param intent used to bind the wallpaper service
79      * @param context Context used to start and bind the live wallpaper service
80      * @param listener if provided, it'll be notified of connection/disconnection events
81      * @param containerView SurfaceView that will display the wallpaper
82      */
WallpaperConnection(Intent intent, Context context, @Nullable WallpaperConnectionListener listener, SurfaceView containerView)83     public WallpaperConnection(Intent intent, Context context,
84             @Nullable WallpaperConnectionListener listener, SurfaceView containerView) {
85         this(intent, context, listener, containerView, null);
86     }
87 
88     /**
89      * @param intent used to bind the wallpaper service
90      * @param context Context used to start and bind the live wallpaper service
91      * @param listener if provided, it'll be notified of connection/disconnection events
92      * @param containerView SurfaceView that will display the wallpaper
93      * @param secondaryContainerView optional SurfaceView that will display a second, mirrored
94      *                               version of the wallpaper
95      */
WallpaperConnection(Intent intent, Context context, @Nullable WallpaperConnectionListener listener, SurfaceView containerView, @Nullable SurfaceView secondaryContainerView)96     public WallpaperConnection(Intent intent, Context context,
97             @Nullable WallpaperConnectionListener listener, SurfaceView containerView,
98             @Nullable SurfaceView secondaryContainerView) {
99         mContext = context.getApplicationContext();
100         mIntent = intent;
101         mListener = listener;
102         mContainerView = containerView;
103         mSecondContainerView = secondaryContainerView;
104     }
105 
106     /**
107      * Bind the Service for this connection.
108      */
connect()109     public boolean connect() {
110         synchronized (this) {
111             if (mConnected) {
112                 return true;
113             }
114             if (!mContext.bindService(mIntent, this,
115                     Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT)) {
116                 return false;
117             }
118 
119             mConnected = true;
120         }
121 
122         if (mListener != null) {
123             mListener.onConnected();
124         }
125 
126         return true;
127     }
128 
129     /**
130      * Disconnect and destroy the WallpaperEngine for this connection.
131      */
disconnect()132     public void disconnect() {
133         synchronized (this) {
134             mConnected = false;
135             if (mEngine != null) {
136                 try {
137                     mEngine.destroy();
138                 } catch (RemoteException e) {
139                     // Ignore
140                 }
141                 mEngine = null;
142             }
143             try {
144                 mContext.unbindService(this);
145             } catch (IllegalArgumentException e) {
146                 Log.i(TAG, "Can't unbind wallpaper service. "
147                         + "It might have crashed, just ignoring.");
148             }
149             mService = null;
150         }
151         if (mListener != null) {
152             mListener.onDisconnected();
153         }
154     }
155 
156     /**
157      * @see ServiceConnection#onServiceConnected(ComponentName, IBinder)
158      */
onServiceConnected(ComponentName name, IBinder service)159     public void onServiceConnected(ComponentName name, IBinder service) {
160         mService = IWallpaperService.Stub.asInterface(service);
161         try {
162             int displayId = mContainerView.getDisplay().getDisplayId();
163 
164             mService.attach(this, mContainerView.getWindowToken(),
165                     LayoutParams.TYPE_APPLICATION_MEDIA,
166                     true, mContainerView.getWidth(), mContainerView.getHeight(),
167                     new Rect(0, 0, 0, 0), displayId);
168         } catch (RemoteException e) {
169             Log.w(TAG, "Failed attaching wallpaper; clearing", e);
170         }
171     }
172 
173     @Override
onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors, int displayId)174     public void onLocalWallpaperColorsChanged(RectF area,
175             WallpaperColors colors, int displayId) {
176 
177     }
178 
179     /**
180      * @see ServiceConnection#onServiceDisconnected(ComponentName)
181      */
onServiceDisconnected(ComponentName name)182     public void onServiceDisconnected(ComponentName name) {
183         mService = null;
184         mEngine = null;
185         Log.w(TAG, "Wallpaper service gone: " + name);
186     }
187 
188     /**
189      * @see IWallpaperConnection#attachEngine(IWallpaperEngine, int)
190      */
attachEngine(IWallpaperEngine engine, int displayId)191     public void attachEngine(IWallpaperEngine engine, int displayId) {
192         synchronized (this) {
193             if (mConnected) {
194                 mEngine = engine;
195                 if (mIsVisible) {
196                     setEngineVisibility(true);
197                 }
198 
199                 // Some wallpapers don't trigger #onWallpaperColorsChanged from remote. Requesting
200                 // wallpaper color here to ensure the #onWallpaperColorsChanged would get called.
201                 try {
202                     mEngine.requestWallpaperColors();
203                 } catch (RemoteException e) {
204                     Log.w(TAG, "Failed requesting wallpaper colors", e);
205                 }
206             } else {
207                 try {
208                     engine.destroy();
209                 } catch (RemoteException e) {
210                     // Ignore
211                 }
212             }
213         }
214     }
215 
216     /**
217      * Returns the engine handled by this WallpaperConnection
218      */
219     @Nullable
getEngine()220     public IWallpaperEngine getEngine() {
221         return mEngine;
222     }
223 
224     /**
225      * @see IWallpaperConnection#setWallpaper(String)
226      */
setWallpaper(String name)227     public ParcelFileDescriptor setWallpaper(String name) {
228         return null;
229     }
230 
231     @Override
onWallpaperColorsChanged(WallpaperColors colors, int displayId)232     public void onWallpaperColorsChanged(WallpaperColors colors, int displayId) {
233         mContainerView.post(() -> {
234             if (mListener != null) {
235                 mListener.onWallpaperColorsChanged(colors, displayId);
236             }
237         });
238     }
239 
240     @Override
engineShown(IWallpaperEngine engine)241     public void engineShown(IWallpaperEngine engine)  {
242         mEngineReady = true;
243         if (mContainerView != null) {
244             mContainerView.post(() -> reparentWallpaperSurface(mContainerView));
245         }
246         if (mSecondContainerView != null) {
247             mSecondContainerView.post(() -> reparentWallpaperSurface(mSecondContainerView));
248         }
249 
250         mContainerView.post(() -> {
251             if (mListener != null) {
252                 mListener.onEngineShown();
253             }
254         });
255     }
256 
257     /**
258      * Returns true if the wallpaper engine has been initialized.
259      */
isEngineReady()260     public boolean isEngineReady() {
261         return mEngineReady;
262     }
263 
264     /**
265      * Sets the engine's visibility.
266      */
setVisibility(boolean visible)267     public void setVisibility(boolean visible) {
268         mIsVisible = visible;
269         setEngineVisibility(visible);
270     }
271 
setEngineVisibility(boolean visible)272     private void setEngineVisibility(boolean visible) {
273         if (mEngine != null && visible != mIsEngineVisible) {
274             try {
275                 mEngine.setVisibility(visible);
276                 mIsEngineVisible = visible;
277             } catch (RemoteException e) {
278                 Log.w(TAG, "Failure setting wallpaper visibility ", e);
279             }
280         }
281     }
282 
reparentWallpaperSurface(SurfaceView parentSurface)283     private void reparentWallpaperSurface(SurfaceView parentSurface) {
284         if (mEngine == null) {
285             Log.i(TAG, "Engine is null, was the service disconnected?");
286             return;
287         }
288         if (parentSurface.getSurfaceControl() != null) {
289             mirrorAndReparent(parentSurface);
290         } else {
291             Log.d(TAG, "SurfaceView not initialized yet, adding callback");
292             parentSurface.getHolder().addCallback(new Callback() {
293                 @Override
294                 public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
295 
296                 }
297 
298                 @Override
299                 public void surfaceCreated(SurfaceHolder surfaceHolder) {
300                     mirrorAndReparent(parentSurface);
301                     parentSurface.getHolder().removeCallback(this);
302                 }
303 
304                 @Override
305                 public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
306 
307                 }
308             });
309         }
310     }
311 
mirrorAndReparent(SurfaceView parentSurface)312     private void mirrorAndReparent(SurfaceView parentSurface) {
313         if (mEngine == null) {
314             Log.i(TAG, "Engine is null, was the service disconnected?");
315             return;
316         }
317         try {
318             SurfaceControl parentSC = parentSurface.getSurfaceControl();
319             SurfaceControl wallpaperMirrorSC = mEngine.mirrorSurfaceControl();
320             if (wallpaperMirrorSC == null) {
321                 return;
322             }
323             float[] values = getScale(parentSurface);
324             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
325             t.setMatrix(wallpaperMirrorSC, values[MSCALE_X], values[MSKEW_Y],
326                     values[MSKEW_X], values[MSCALE_Y]);
327             t.reparent(wallpaperMirrorSC, parentSC);
328             t.show(wallpaperMirrorSC);
329             t.apply();
330         } catch (RemoteException e) {
331             Log.e(TAG, "Couldn't reparent wallpaper surface", e);
332         }
333     }
334 
getScale(SurfaceView parentSurface)335     private float[] getScale(SurfaceView parentSurface) {
336         Matrix m = new Matrix();
337         float[] values = new float[9];
338         Rect surfacePosition = parentSurface.getHolder().getSurfaceFrame();
339         DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics(
340                 mContainerView.getResources(), mContainerView.getDisplay());
341         m.postScale(((float) surfacePosition.width()) / metrics.widthPixels,
342                 ((float) surfacePosition.height()) / metrics.heightPixels);
343         m.getValues(values);
344         return values;
345     }
346 
347     /**
348      * Interface to be notified of connect/disconnect events from {@link WallpaperConnection}
349      */
350     public interface WallpaperConnectionListener {
351         /**
352          * Called after the Wallpaper service has been bound.
353          */
onConnected()354         default void onConnected() {}
355 
356         /**
357          * Called after the Wallpaper engine has been terminated and the service has been unbound.
358          */
onDisconnected()359         default void onDisconnected() {}
360 
361         /**
362          * Called after the wallpaper has been rendered for the first time.
363          */
onEngineShown()364         default void onEngineShown() {}
365 
366         /**
367          * Called after the wallpaper color is available or updated.
368          */
onWallpaperColorsChanged(WallpaperColors colors, int displayId)369         default void onWallpaperColorsChanged(WallpaperColors colors, int displayId) {}
370     }
371 }
372