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