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