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