• 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.app.Flags.liveWallpaperContentHandling;
19 import static android.graphics.Matrix.MSCALE_X;
20 import static android.graphics.Matrix.MSCALE_Y;
21 import static android.graphics.Matrix.MSKEW_X;
22 import static android.graphics.Matrix.MSKEW_Y;
23 
24 import android.app.WallpaperColors;
25 import android.app.WallpaperInfo;
26 import android.app.WallpaperManager;
27 import android.app.wallpaper.WallpaperDescription;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.ServiceConnection;
32 import android.graphics.Matrix;
33 import android.graphics.Point;
34 import android.graphics.Rect;
35 import android.graphics.RectF;
36 import android.os.Bundle;
37 import android.os.IBinder;
38 import android.os.Looper;
39 import android.os.ParcelFileDescriptor;
40 import android.os.RemoteException;
41 import android.service.wallpaper.IWallpaperConnection;
42 import android.service.wallpaper.IWallpaperEngine;
43 import android.service.wallpaper.IWallpaperService;
44 import android.util.Log;
45 import android.view.Display;
46 import android.view.SurfaceControl;
47 import android.view.SurfaceHolder;
48 import android.view.SurfaceHolder.Callback;
49 import android.view.SurfaceView;
50 import android.view.View;
51 import android.view.WindowManager.LayoutParams;
52 
53 import androidx.annotation.NonNull;
54 import androidx.annotation.Nullable;
55 
56 import java.lang.reflect.InvocationTargetException;
57 import java.lang.reflect.Method;
58 import java.util.ArrayList;
59 import java.util.List;
60 
61 /**
62  * Implementation of {@link IWallpaperConnection} that handles communication with a
63  * {@link android.service.wallpaper.WallpaperService}
64  */
65 public class WallpaperConnection extends IWallpaperConnection.Stub implements ServiceConnection {
66 
67     /**
68      * Defines different possible scenarios for which we need to dispatch a command from picker to
69      * the wallpaper.
70      */
71     public enum WhichPreview {
72         /**
73          * Represents the case when we preview a currently applied wallpaper (home/lock) simply
74          * by tapping on it.
75          */
76         PREVIEW_CURRENT(0),
77         /**
78          * Represents the case when we are editing the currently applied wallpaper.
79          */
80         EDIT_CURRENT(1),
81         /**
82          * Represents the case when we are editing a wallpaper that's not currently applied.
83          */
84         EDIT_NON_CURRENT(2);
85 
86         private final int mValue;
87 
WhichPreview(int value)88         WhichPreview(int value) {
89             this.mValue = value;
90         }
91 
getValue()92         public int getValue() {
93             return mValue;
94         }
95     }
96 
97     /**
98      * Returns whether live preview is available in framework.
99      */
isPreviewAvailable()100     public static boolean isPreviewAvailable() {
101         try {
102             return IWallpaperEngine.class.getMethod("mirrorSurfaceControl") != null;
103         } catch (NoSuchMethodException | SecurityException e) {
104             return false;
105         }
106     }
107 
108     private static final String TAG = "WallpaperConnection";
109     private static final Looper sMainLooper = Looper.getMainLooper();
110     private final Context mContext;
111     private final Intent mIntent;
112     private final List<SurfaceControl> mMirrorSurfaceControls = new ArrayList<>();
113     private WallpaperConnectionListener mListener;
114     private SurfaceView mContainerView;
115     private SurfaceView mSecondContainerView;
116     private IWallpaperService mService;
117     @Nullable private IWallpaperEngine mEngine;
118     @Nullable private Point mDisplayMetrics;
119     private boolean mConnected;
120     private boolean mIsVisible;
121     private boolean mIsEngineVisible;
122     private boolean mEngineReady;
123     private boolean mDestroyed;
124     private int mDestinationFlag;
125     private WhichPreview mWhichPreview;
126     @NonNull private final WallpaperDescription mDescription;
127     private IBinder mToken;
128 
129     /**
130      * @param intent used to bind the wallpaper service
131      * @param context Context used to start and bind the live wallpaper service
132      * @param listener if provided, it'll be notified of connection/disconnection events
133      * @param containerView SurfaceView that will display the wallpaper
134      */
WallpaperConnection(Intent intent, Context context, @Nullable WallpaperConnectionListener listener, @NonNull SurfaceView containerView, WhichPreview preview)135     public WallpaperConnection(Intent intent, Context context,
136             @Nullable WallpaperConnectionListener listener, @NonNull SurfaceView containerView,
137             WhichPreview preview) {
138         this(intent, context, listener, containerView, null, null,
139                 preview, new WallpaperDescription.Builder().build());
140     }
141 
142     /**
143      * @param intent used to bind the wallpaper service
144      * @param context Context used to start and bind the live wallpaper service
145      * @param listener if provided, it'll be notified of connection/disconnection events
146      * @param containerView SurfaceView that will display the wallpaper
147      * @param secondaryContainerView optional SurfaceView that will display a second, mirrored
148      *                               version of the wallpaper
149      * @param destinationFlag one of WallpaperManager.FLAG_SYSTEM, WallpaperManager.FLAG_LOCK
150      *                        indicating for which screen we're previewing the wallpaper, or null if
151      *                        unknown
152      * @param preview describes type of preview being shown
153      * @param description optional content to pass to wallpaper engine
154      *
155      */
WallpaperConnection(Intent intent, Context context, @Nullable WallpaperConnectionListener listener, @NonNull SurfaceView containerView, @Nullable SurfaceView secondaryContainerView, @Nullable @WallpaperManager.SetWallpaperFlags Integer destinationFlag, WhichPreview preview, @NonNull WallpaperDescription description)156     public WallpaperConnection(Intent intent, Context context,
157             @Nullable WallpaperConnectionListener listener, @NonNull SurfaceView containerView,
158             @Nullable SurfaceView secondaryContainerView,
159             @Nullable @WallpaperManager.SetWallpaperFlags Integer destinationFlag,
160             WhichPreview preview, @NonNull WallpaperDescription description) {
161         mContext = context.getApplicationContext();
162         mIntent = intent;
163         mListener = listener;
164         mContainerView = containerView;
165         mSecondContainerView = secondaryContainerView;
166         mDestinationFlag = destinationFlag == null ? WallpaperManager.FLAG_SYSTEM : destinationFlag;
167         mWhichPreview = preview;
168         mDescription = description;
169     }
170 
171     /**
172      * Bind the Service for this connection.
173      */
connect()174     public boolean connect() {
175         if (mDestroyed) {
176             throw new IllegalStateException("Cannot connect on a destroyed WallpaperConnection");
177         }
178         synchronized (this) {
179             if (mConnected) {
180                 return true;
181             }
182             if (!mContext.bindService(mIntent, this,
183                     Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT
184                             | Context.BIND_ALLOW_ACTIVITY_STARTS)) {
185                 return false;
186             }
187 
188             mConnected = true;
189         }
190 
191         if (mListener != null) {
192             mListener.onConnected();
193         }
194 
195         return true;
196     }
197 
198     /**
199      * Disconnect and destroy the WallpaperEngine for this connection.
200      */
disconnect()201     public void disconnect() {
202         mConnected = false;
203         destroyEngine();
204         unbindService();
205         if (mListener != null) {
206             mListener.onDisconnected();
207         }
208     }
209 
destroyEngine()210     private synchronized void destroyEngine() {
211         if (mEngine == null) {
212             return;
213         }
214 
215         try {
216             mEngine.destroy();
217             for (SurfaceControl control : mMirrorSurfaceControls) {
218                 control.release();
219             }
220             mMirrorSurfaceControls.clear();
221         } catch (RemoteException e) {
222             // Ignore
223         }
224         mEngine = null;
225     }
226 
227     /**
228      * Detach the connection from wallpaper service. Generally this does not need to be called
229      * throughout an activity's active lifecycle since the same connection is used across
230      * WallpaperConnection instances, for views within the same window. Calling attachConnection
231      * should be enough to overwrite the previous connection.
232      */
detachConnection()233     public synchronized void detachConnection() {
234         if (mService != null) {
235             try {
236                 mService.detach(mToken);
237             } catch (RemoteException e) {
238                 Log.i(TAG, "Can't detach wallpaper service.");
239             }
240         }
241         mToken = null;
242     }
243 
unbindService()244     private synchronized void unbindService() {
245         try {
246             mContext.unbindService(this);
247         } catch (IllegalArgumentException e) {
248             Log.i(TAG, "Can't unbind wallpaper service. "
249                     + "It might have crashed, just ignoring.");
250         }
251         mService = null;
252     }
253 
254     /**
255      * Clean up references on this WallpaperConnection.
256      * After calling this method, {@link #connect()} cannot be called again.
257      */
destroy()258     public void destroy() {
259         disconnect();
260         mContainerView = null;
261         mSecondContainerView = null;
262         mListener = null;
263         mDestroyed = true;
264     }
265 
266     /**
267      * @see ServiceConnection#onServiceConnected(ComponentName, IBinder)
268      */
onServiceConnected(ComponentName name, IBinder service)269     public void onServiceConnected(ComponentName name, IBinder service) {
270         if (mContainerView == null) {
271             return;
272         }
273         mService = IWallpaperService.Stub.asInterface(service);
274         if (mContainerView.getDisplay() == null) {
275             mContainerView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
276                 @Override
277                 public void onViewAttachedToWindow(View v) {
278                     attachConnection(v.getDisplay().getDisplayId());
279                     mContainerView.removeOnAttachStateChangeListener(this);
280                 }
281 
282                 @Override
283                 public void onViewDetachedFromWindow(View v) {}
284             });
285         } else {
286             attachConnection(mContainerView.getDisplay().getDisplayId());
287         }
288     }
289 
290     @Override
onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors, int displayId)291     public void onLocalWallpaperColorsChanged(RectF area,
292             WallpaperColors colors, int displayId) {
293 
294     }
295 
296     /**
297      * @see ServiceConnection#onServiceDisconnected(ComponentName)
298      */
onServiceDisconnected(ComponentName name)299     public void onServiceDisconnected(ComponentName name) {
300         mService = null;
301         mEngine = null;
302         Log.w(TAG, "Wallpaper service gone: " + name);
303     }
304 
305     /**
306      * @see IWallpaperConnection#attachEngine(IWallpaperEngine, int)
307      */
attachEngine(IWallpaperEngine engine, int displayId)308     public void attachEngine(IWallpaperEngine engine, int displayId) {
309         synchronized (this) {
310             if (mConnected) {
311                 mEngine = engine;
312                 if (mIsVisible) {
313                     setEngineVisibility(true);
314                 }
315 
316                 try {
317                     Point displayMetrics = getDisplayMetrics();
318                     // Reset the live wallpaper preview with the correct screen dimensions. It is
319                     // a known issue that the wallpaper service maybe get the Activity window size
320                     // which may differ from the actual physical device screen size, e.g. when in
321                     // 2-pane mode.
322                     // TODO b/262750854 Fix wallpaper service to get the actual physical device
323                     //      screen size instead of the window size that might be smaller when in
324                     //      2-pane mode.
325                     mEngine.resizePreview(new Rect(0, 0, displayMetrics.x, displayMetrics.y));
326                     // Some wallpapers don't trigger #onWallpaperColorsChanged from remote.
327                     // Requesting wallpaper color here to ensure the #onWallpaperColorsChanged
328                     // would get called.
329                     mEngine.requestWallpaperColors();
330                 } catch (RemoteException | NullPointerException e) {
331                     Log.w(TAG, "Failed calling WallpaperEngine APIs", e);
332                 }
333             } else {
334                 try {
335                     engine.destroy();
336                 } catch (RemoteException e) {
337                     // Ignore
338                 }
339             }
340         }
341     }
342 
343     /**
344      * Returns the engine handled by this WallpaperConnection
345      */
346     @Nullable
getEngine()347     public IWallpaperEngine getEngine() {
348         return mEngine;
349     }
350 
351     /**
352      * @see IWallpaperConnection#setWallpaper(String)
353      */
setWallpaper(String name)354     public ParcelFileDescriptor setWallpaper(String name) {
355         return null;
356     }
357 
358     @Override
onWallpaperColorsChanged(WallpaperColors colors, int displayId)359     public void onWallpaperColorsChanged(WallpaperColors colors, int displayId) {
360         if (mContainerView != null) {
361             mContainerView.post(() -> {
362                 if (mListener != null) {
363                     mListener.onWallpaperColorsChanged(colors, displayId);
364                 }
365             });
366         }
367     }
368 
369     @Override
engineShown(IWallpaperEngine engine)370     public void engineShown(IWallpaperEngine engine) {
371         mEngineReady = true;
372         Bundle bundle = new Bundle();
373         bundle.putInt("which_preview", mWhichPreview.getValue());
374         try {
375             engine.dispatchWallpaperCommand("android.wallpaper.previewinfo", 0, 0, 0, bundle);
376         } catch (RemoteException e) {
377             Log.e(TAG, "Error dispatching wallpaper command: " + mWhichPreview.toString());
378         }
379         if (mContainerView != null) {
380             mContainerView.post(() -> reparentWallpaperSurface(mContainerView));
381         }
382         if (mSecondContainerView != null) {
383             mSecondContainerView.post(() -> reparentWallpaperSurface(mSecondContainerView));
384         }
385         if (mContainerView != null) {
386             mContainerView.post(() -> {
387                 if (mListener != null) {
388                     mListener.onEngineShown();
389                 }
390             });
391         }
392     }
393 
394     /**
395      * Returns true if the wallpaper engine has been initialized.
396      */
isEngineReady()397     public boolean isEngineReady() {
398         return mEngineReady;
399     }
400 
401     /**
402      * Sets the engine's visibility.
403      */
setVisibility(boolean visible)404     public void setVisibility(boolean visible) {
405         synchronized (this) {
406             mIsVisible = visible;
407             setEngineVisibility(visible);
408         }
409     }
410 
411 
412     /**
413      * Set the {@link android.app.WallpaperManager.SetWallpaperFlags} to the Engine to indicate
414      * which screen it's being applied/previewed to.
415      */
setWallpaperFlags(@allpaperManager.SetWallpaperFlags int wallpaperFlags)416     public void setWallpaperFlags(@WallpaperManager.SetWallpaperFlags int wallpaperFlags)
417             throws RemoteException {
418         if (mEngine != null && mEngineReady) {
419             mEngine.setWallpaperFlags(wallpaperFlags);
420         }
421     }
422 
423     /*
424      * Tries to call the attach method used in Android 14(U) and earlier, returning true on success
425      * otherwise false.
426      */
tryPreUAttach(int displayId)427     private boolean tryPreUAttach(int displayId) {
428         try {
429             Method preUMethod = mService.getClass().getMethod("attach",
430                     IWallpaperConnection.class, IBinder.class, int.class, boolean.class,
431                     int.class, int.class, Rect.class, int.class);
432             preUMethod.invoke(mService, this, mToken, LayoutParams.TYPE_APPLICATION_MEDIA, true,
433                     mContainerView.getWidth(), mContainerView.getHeight(), new Rect(0, 0, 0, 0),
434                     displayId);
435             Log.d(TAG, "Using pre-U version of IWallpaperService#attach");
436             if (liveWallpaperContentHandling()) {
437                 Log.w(TAG,
438                         "live wallpaper content handling enabled, but pre-U attach method called");
439             }
440             return true;
441         } catch (NoSuchMethodException | NoSuchMethodError | InvocationTargetException
442                  | IllegalAccessException e) {
443             return false;
444         }
445     }
446 
447     /*
448      * Tries to call the attach method used in Android 16(B) and earlier, returning true on success
449      * otherwise false.
450      */
tryPreBAttach(int displayId)451     private boolean tryPreBAttach(int displayId) {
452         try {
453             Method preBMethod = mService.getClass().getMethod("attach",
454                     IWallpaperConnection.class, IBinder.class, int.class, boolean.class,
455                     int.class, int.class, Rect.class, int.class, WallpaperInfo.class);
456             preBMethod.invoke(mService, this, mToken, LayoutParams.TYPE_APPLICATION_MEDIA, true,
457                     mContainerView.getWidth(), mContainerView.getHeight(), new Rect(0, 0, 0, 0),
458                     displayId, mDestinationFlag, null);
459             if (liveWallpaperContentHandling()) {
460                 Log.w(TAG,
461                         "live wallpaper content handling enabled, but pre-B attach method called");
462             }
463             return true;
464         } catch (NoSuchMethodException | NoSuchMethodError | InvocationTargetException
465                  | IllegalAccessException e) {
466             return false;
467         }
468     }
469 
470     /*
471      * This method tries to call historical versions of IWallpaperService#attach since this code
472      * may be running against older versions of Android. We have no control over what versions of
473      * Android third party users of this code will be running.
474      */
attachConnection(int displayId)475     private void attachConnection(int displayId) {
476         mToken = mContainerView.getWindowToken();
477 
478         try {
479             if (tryPreUAttach(displayId)) return;
480             if (tryPreBAttach(displayId)) return;
481 
482             mService.attach(this, mToken, LayoutParams.TYPE_APPLICATION_MEDIA, true,
483                     mContainerView.getWidth(), mContainerView.getHeight(), new Rect(0, 0, 0, 0),
484                     displayId, mDestinationFlag, null, mDescription);
485         } catch (RemoteException e) {
486             Log.w(TAG, "Failed attaching wallpaper; clearing", e);
487         }
488     }
489 
setEngineVisibility(boolean visible)490     private void setEngineVisibility(boolean visible) {
491         if (mEngine != null && visible != mIsEngineVisible) {
492             try {
493                 mEngine.setVisibility(visible);
494                 mIsEngineVisible = visible;
495             } catch (RemoteException e) {
496                 Log.w(TAG, "Failure setting wallpaper visibility ", e);
497             }
498         }
499     }
500 
reparentWallpaperSurface(SurfaceView parentSurface)501     private void reparentWallpaperSurface(SurfaceView parentSurface) {
502         if (parentSurface == null) {
503             return;
504         }
505         synchronized (this) {
506             if (mEngine == null) {
507                 Log.i(TAG, "Engine is null, was the service disconnected?");
508                 return;
509             }
510         }
511         if (parentSurface.getSurfaceControl() != null) {
512             mirrorAndReparent(parentSurface);
513         } else {
514             Log.d(TAG, "SurfaceView not initialized yet, adding callback");
515             parentSurface.getHolder().addCallback(new Callback() {
516                 @Override
517                 public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
518 
519                 }
520 
521                 @Override
522                 public void surfaceCreated(SurfaceHolder surfaceHolder) {
523                     mirrorAndReparent(parentSurface);
524                     parentSurface.getHolder().removeCallback(this);
525                 }
526 
527                 @Override
528                 public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
529 
530                 }
531             });
532         }
533     }
534 
mirrorAndReparent(SurfaceView parentSurface)535     private void mirrorAndReparent(SurfaceView parentSurface) {
536         IWallpaperEngine engine;
537         synchronized (this) {
538             if (mEngine == null) {
539                 Log.i(TAG, "Engine is null, was the service disconnected?");
540                 return;
541             }
542             engine = mEngine;
543         }
544         try {
545             SurfaceControl parentSC = parentSurface.getSurfaceControl();
546             SurfaceControl wallpaperMirrorSC = engine.mirrorSurfaceControl();
547             if (wallpaperMirrorSC == null) {
548                 return;
549             }
550             float[] values = getScale(parentSurface);
551             try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
552                 t.setMatrix(wallpaperMirrorSC, values[MSCALE_X], values[MSKEW_Y],
553                         values[MSKEW_X], values[MSCALE_Y]);
554                 t.reparent(wallpaperMirrorSC, parentSC);
555                 t.show(wallpaperMirrorSC);
556                 t.apply();
557             }
558             synchronized (this) {
559                 mMirrorSurfaceControls.add(wallpaperMirrorSC);
560             }
561         } catch (RemoteException | NullPointerException e) {
562             Log.e(TAG, "Couldn't reparent wallpaper surface", e);
563         }
564     }
565 
getScale(SurfaceView parentSurface)566     private float[] getScale(SurfaceView parentSurface) {
567         Matrix m = new Matrix();
568         float[] values = new float[9];
569         Rect surfacePosition = parentSurface.getHolder().getSurfaceFrame();
570         Point displayMetrics = getDisplayMetrics();
571         m.postScale(((float) surfacePosition.width()) / displayMetrics.x,
572                 ((float) surfacePosition.height()) / displayMetrics.y);
573         m.getValues(values);
574         return values;
575     }
576 
577     /**
578      * Get display metrics. Only call this when the display is attached to the window.
579      */
getDisplayMetrics()580     private Point getDisplayMetrics() {
581         if (mDisplayMetrics != null) {
582             return mDisplayMetrics;
583         }
584         ScreenSizeCalculator screenSizeCalculator = ScreenSizeCalculator.getInstance();
585         Display display = mContainerView.getDisplay();
586         if (display == null) {
587             throw new NullPointerException(
588                     "Display is null due to the view not currently attached to a window.");
589         }
590         mDisplayMetrics = screenSizeCalculator.getScreenSize(display);
591         return mDisplayMetrics;
592     }
593 
594     /**
595      * Interface to be notified of connect/disconnect events from {@link WallpaperConnection}
596      */
597     public interface WallpaperConnectionListener {
598         /**
599          * Called after the Wallpaper service has been bound.
600          */
onConnected()601         default void onConnected() {}
602 
603         /**
604          * Called after the Wallpaper engine has been terminated and the service has been unbound.
605          */
onDisconnected()606         default void onDisconnected() {}
607 
608         /**
609          * Called after the wallpaper has been rendered for the first time.
610          */
onEngineShown()611         default void onEngineShown() {}
612 
613         /**
614          * Called after the wallpaper color is available or updated.
615          */
onWallpaperColorsChanged(WallpaperColors colors, int displayId)616         default void onWallpaperColorsChanged(WallpaperColors colors, int displayId) {}
617     }
618 }
619