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.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.ServiceConnection; 28 import android.graphics.Matrix; 29 import android.graphics.Rect; 30 import android.graphics.RectF; 31 import android.os.IBinder; 32 import android.os.ParcelFileDescriptor; 33 import android.os.RemoteException; 34 import android.service.wallpaper.IWallpaperConnection; 35 import android.service.wallpaper.IWallpaperEngine; 36 import android.service.wallpaper.IWallpaperService; 37 import android.util.DisplayMetrics; 38 import android.util.Log; 39 import android.view.SurfaceControl; 40 import android.view.SurfaceHolder; 41 import android.view.SurfaceHolder.Callback; 42 import android.view.SurfaceView; 43 import android.view.WindowManager.LayoutParams; 44 45 import androidx.annotation.Nullable; 46 47 /** 48 * Implementation of {@link IWallpaperConnection} that handles communication with a 49 * {@link android.service.wallpaper.WallpaperService} 50 */ 51 public class WallpaperConnection extends IWallpaperConnection.Stub implements ServiceConnection { 52 53 /** 54 * Returns whether live preview is available in framework. 55 */ isPreviewAvailable()56 public static boolean isPreviewAvailable() { 57 try { 58 return IWallpaperEngine.class.getMethod("mirrorSurfaceControl") != null; 59 } catch (NoSuchMethodException | SecurityException e) { 60 return false; 61 } 62 } 63 64 private static final String TAG = "WallpaperConnection"; 65 private final Context mContext; 66 private final Intent mIntent; 67 private final WallpaperConnectionListener mListener; 68 private final SurfaceView mContainerView; 69 private final SurfaceView mSecondContainerView; 70 private IWallpaperService mService; 71 @Nullable private IWallpaperEngine mEngine; 72 private boolean mConnected; 73 private boolean mIsVisible; 74 private boolean mIsEngineVisible; 75 private boolean mEngineReady; 76 77 /** 78 * @param intent used to bind the wallpaper service 79 * @param context Context used to start and bind the live wallpaper service 80 * @param listener if provided, it'll be notified of connection/disconnection events 81 * @param containerView SurfaceView that will display the wallpaper 82 */ WallpaperConnection(Intent intent, Context context, @Nullable WallpaperConnectionListener listener, SurfaceView containerView)83 public WallpaperConnection(Intent intent, Context context, 84 @Nullable WallpaperConnectionListener listener, SurfaceView containerView) { 85 this(intent, context, listener, containerView, null); 86 } 87 88 /** 89 * @param intent used to bind the wallpaper service 90 * @param context Context used to start and bind the live wallpaper service 91 * @param listener if provided, it'll be notified of connection/disconnection events 92 * @param containerView SurfaceView that will display the wallpaper 93 * @param secondaryContainerView optional SurfaceView that will display a second, mirrored 94 * version of the wallpaper 95 */ WallpaperConnection(Intent intent, Context context, @Nullable WallpaperConnectionListener listener, SurfaceView containerView, @Nullable SurfaceView secondaryContainerView)96 public WallpaperConnection(Intent intent, Context context, 97 @Nullable WallpaperConnectionListener listener, SurfaceView containerView, 98 @Nullable SurfaceView secondaryContainerView) { 99 mContext = context.getApplicationContext(); 100 mIntent = intent; 101 mListener = listener; 102 mContainerView = containerView; 103 mSecondContainerView = secondaryContainerView; 104 } 105 106 /** 107 * Bind the Service for this connection. 108 */ connect()109 public boolean connect() { 110 synchronized (this) { 111 if (mConnected) { 112 return true; 113 } 114 if (!mContext.bindService(mIntent, this, 115 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT)) { 116 return false; 117 } 118 119 mConnected = true; 120 } 121 122 if (mListener != null) { 123 mListener.onConnected(); 124 } 125 126 return true; 127 } 128 129 /** 130 * Disconnect and destroy the WallpaperEngine for this connection. 131 */ disconnect()132 public void disconnect() { 133 synchronized (this) { 134 mConnected = false; 135 if (mEngine != null) { 136 try { 137 mEngine.destroy(); 138 } catch (RemoteException e) { 139 // Ignore 140 } 141 mEngine = null; 142 } 143 try { 144 mContext.unbindService(this); 145 } catch (IllegalArgumentException e) { 146 Log.i(TAG, "Can't unbind wallpaper service. " 147 + "It might have crashed, just ignoring."); 148 } 149 mService = null; 150 } 151 if (mListener != null) { 152 mListener.onDisconnected(); 153 } 154 } 155 156 /** 157 * @see ServiceConnection#onServiceConnected(ComponentName, IBinder) 158 */ onServiceConnected(ComponentName name, IBinder service)159 public void onServiceConnected(ComponentName name, IBinder service) { 160 mService = IWallpaperService.Stub.asInterface(service); 161 try { 162 int displayId = mContainerView.getDisplay().getDisplayId(); 163 164 mService.attach(this, mContainerView.getWindowToken(), 165 LayoutParams.TYPE_APPLICATION_MEDIA, 166 true, mContainerView.getWidth(), mContainerView.getHeight(), 167 new Rect(0, 0, 0, 0), displayId); 168 } catch (RemoteException e) { 169 Log.w(TAG, "Failed attaching wallpaper; clearing", e); 170 } 171 } 172 173 @Override onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors, int displayId)174 public void onLocalWallpaperColorsChanged(RectF area, 175 WallpaperColors colors, int displayId) { 176 177 } 178 179 /** 180 * @see ServiceConnection#onServiceDisconnected(ComponentName) 181 */ onServiceDisconnected(ComponentName name)182 public void onServiceDisconnected(ComponentName name) { 183 mService = null; 184 mEngine = null; 185 Log.w(TAG, "Wallpaper service gone: " + name); 186 } 187 188 /** 189 * @see IWallpaperConnection#attachEngine(IWallpaperEngine, int) 190 */ attachEngine(IWallpaperEngine engine, int displayId)191 public void attachEngine(IWallpaperEngine engine, int displayId) { 192 synchronized (this) { 193 if (mConnected) { 194 mEngine = engine; 195 if (mIsVisible) { 196 setEngineVisibility(true); 197 } 198 199 // Some wallpapers don't trigger #onWallpaperColorsChanged from remote. Requesting 200 // wallpaper color here to ensure the #onWallpaperColorsChanged would get called. 201 try { 202 mEngine.requestWallpaperColors(); 203 } catch (RemoteException e) { 204 Log.w(TAG, "Failed requesting wallpaper colors", e); 205 } 206 } else { 207 try { 208 engine.destroy(); 209 } catch (RemoteException e) { 210 // Ignore 211 } 212 } 213 } 214 } 215 216 /** 217 * Returns the engine handled by this WallpaperConnection 218 */ 219 @Nullable getEngine()220 public IWallpaperEngine getEngine() { 221 return mEngine; 222 } 223 224 /** 225 * @see IWallpaperConnection#setWallpaper(String) 226 */ setWallpaper(String name)227 public ParcelFileDescriptor setWallpaper(String name) { 228 return null; 229 } 230 231 @Override onWallpaperColorsChanged(WallpaperColors colors, int displayId)232 public void onWallpaperColorsChanged(WallpaperColors colors, int displayId) { 233 mContainerView.post(() -> { 234 if (mListener != null) { 235 mListener.onWallpaperColorsChanged(colors, displayId); 236 } 237 }); 238 } 239 240 @Override engineShown(IWallpaperEngine engine)241 public void engineShown(IWallpaperEngine engine) { 242 mEngineReady = true; 243 if (mContainerView != null) { 244 mContainerView.post(() -> reparentWallpaperSurface(mContainerView)); 245 } 246 if (mSecondContainerView != null) { 247 mSecondContainerView.post(() -> reparentWallpaperSurface(mSecondContainerView)); 248 } 249 250 mContainerView.post(() -> { 251 if (mListener != null) { 252 mListener.onEngineShown(); 253 } 254 }); 255 } 256 257 /** 258 * Returns true if the wallpaper engine has been initialized. 259 */ isEngineReady()260 public boolean isEngineReady() { 261 return mEngineReady; 262 } 263 264 /** 265 * Sets the engine's visibility. 266 */ setVisibility(boolean visible)267 public void setVisibility(boolean visible) { 268 mIsVisible = visible; 269 setEngineVisibility(visible); 270 } 271 setEngineVisibility(boolean visible)272 private void setEngineVisibility(boolean visible) { 273 if (mEngine != null && visible != mIsEngineVisible) { 274 try { 275 mEngine.setVisibility(visible); 276 mIsEngineVisible = visible; 277 } catch (RemoteException e) { 278 Log.w(TAG, "Failure setting wallpaper visibility ", e); 279 } 280 } 281 } 282 reparentWallpaperSurface(SurfaceView parentSurface)283 private void reparentWallpaperSurface(SurfaceView parentSurface) { 284 if (mEngine == null) { 285 Log.i(TAG, "Engine is null, was the service disconnected?"); 286 return; 287 } 288 if (parentSurface.getSurfaceControl() != null) { 289 mirrorAndReparent(parentSurface); 290 } else { 291 Log.d(TAG, "SurfaceView not initialized yet, adding callback"); 292 parentSurface.getHolder().addCallback(new Callback() { 293 @Override 294 public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { 295 296 } 297 298 @Override 299 public void surfaceCreated(SurfaceHolder surfaceHolder) { 300 mirrorAndReparent(parentSurface); 301 parentSurface.getHolder().removeCallback(this); 302 } 303 304 @Override 305 public void surfaceDestroyed(SurfaceHolder surfaceHolder) { 306 307 } 308 }); 309 } 310 } 311 mirrorAndReparent(SurfaceView parentSurface)312 private void mirrorAndReparent(SurfaceView parentSurface) { 313 if (mEngine == null) { 314 Log.i(TAG, "Engine is null, was the service disconnected?"); 315 return; 316 } 317 try { 318 SurfaceControl parentSC = parentSurface.getSurfaceControl(); 319 SurfaceControl wallpaperMirrorSC = mEngine.mirrorSurfaceControl(); 320 if (wallpaperMirrorSC == null) { 321 return; 322 } 323 float[] values = getScale(parentSurface); 324 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 325 t.setMatrix(wallpaperMirrorSC, values[MSCALE_X], values[MSKEW_Y], 326 values[MSKEW_X], values[MSCALE_Y]); 327 t.reparent(wallpaperMirrorSC, parentSC); 328 t.show(wallpaperMirrorSC); 329 t.apply(); 330 } catch (RemoteException e) { 331 Log.e(TAG, "Couldn't reparent wallpaper surface", e); 332 } 333 } 334 getScale(SurfaceView parentSurface)335 private float[] getScale(SurfaceView parentSurface) { 336 Matrix m = new Matrix(); 337 float[] values = new float[9]; 338 Rect surfacePosition = parentSurface.getHolder().getSurfaceFrame(); 339 DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics( 340 mContainerView.getResources(), mContainerView.getDisplay()); 341 m.postScale(((float) surfacePosition.width()) / metrics.widthPixels, 342 ((float) surfacePosition.height()) / metrics.heightPixels); 343 m.getValues(values); 344 return values; 345 } 346 347 /** 348 * Interface to be notified of connect/disconnect events from {@link WallpaperConnection} 349 */ 350 public interface WallpaperConnectionListener { 351 /** 352 * Called after the Wallpaper service has been bound. 353 */ onConnected()354 default void onConnected() {} 355 356 /** 357 * Called after the Wallpaper engine has been terminated and the service has been unbound. 358 */ onDisconnected()359 default void onDisconnected() {} 360 361 /** 362 * Called after the wallpaper has been rendered for the first time. 363 */ onEngineShown()364 default void onEngineShown() {} 365 366 /** 367 * Called after the wallpaper color is available or updated. 368 */ onWallpaperColorsChanged(WallpaperColors colors, int displayId)369 default void onWallpaperColorsChanged(WallpaperColors colors, int displayId) {} 370 } 371 } 372