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