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