blob: 06b4619ae4b58d00ad244edd5a83eb8ea117c5a3 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wallpaper.util;
import static android.graphics.Matrix.MSCALE_X;
import static android.graphics.Matrix.MSCALE_Y;
import static android.graphics.Matrix.MSKEW_X;
import static android.graphics.Matrix.MSKEW_Y;
import android.app.WallpaperColors;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.service.wallpaper.IWallpaperConnection;
import android.service.wallpaper.IWallpaperEngine;
import android.service.wallpaper.IWallpaperService;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.WindowManager.LayoutParams;
import androidx.annotation.Nullable;
/**
* Implementation of {@link IWallpaperConnection} that handles communication with a
* {@link android.service.wallpaper.WallpaperService}
*/
public class WallpaperConnection extends IWallpaperConnection.Stub implements ServiceConnection {
/**
* Returns whether live preview is available in framework.
*/
public static boolean isPreviewAvailable() {
try {
return IWallpaperEngine.class.getMethod("mirrorSurfaceControl") != null;
} catch (NoSuchMethodException | SecurityException e) {
return false;
}
}
private static final String TAG = "WallpaperConnection";
private final Context mContext;
private final Intent mIntent;
private final WallpaperConnectionListener mListener;
private final SurfaceView mContainerView;
private final SurfaceView mSecondContainerView;
private IWallpaperService mService;
@Nullable private IWallpaperEngine mEngine;
private boolean mConnected;
private boolean mIsVisible;
private boolean mIsEngineVisible;
private boolean mEngineReady;
/**
* @param intent used to bind the wallpaper service
* @param context Context used to start and bind the live wallpaper service
* @param listener if provided, it'll be notified of connection/disconnection events
* @param containerView SurfaceView that will display the wallpaper
*/
public WallpaperConnection(Intent intent, Context context,
@Nullable WallpaperConnectionListener listener, SurfaceView containerView) {
this(intent, context, listener, containerView, null);
}
/**
* @param intent used to bind the wallpaper service
* @param context Context used to start and bind the live wallpaper service
* @param listener if provided, it'll be notified of connection/disconnection events
* @param containerView SurfaceView that will display the wallpaper
* @param secondaryContainerView optional SurfaceView that will display a second, mirrored
* version of the wallpaper
*/
public WallpaperConnection(Intent intent, Context context,
@Nullable WallpaperConnectionListener listener, SurfaceView containerView,
@Nullable SurfaceView secondaryContainerView) {
mContext = context.getApplicationContext();
mIntent = intent;
mListener = listener;
mContainerView = containerView;
mSecondContainerView = secondaryContainerView;
}
/**
* Bind the Service for this connection.
*/
public boolean connect() {
synchronized (this) {
if (mConnected) {
return true;
}
if (!mContext.bindService(mIntent, this,
Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT)) {
return false;
}
mConnected = true;
}
if (mListener != null) {
mListener.onConnected();
}
return true;
}
/**
* Disconnect and destroy the WallpaperEngine for this connection.
*/
public void disconnect() {
synchronized (this) {
mConnected = false;
if (mEngine != null) {
try {
mEngine.destroy();
} catch (RemoteException e) {
// Ignore
}
mEngine = null;
}
try {
mContext.unbindService(this);
} catch (IllegalArgumentException e) {
Log.i(TAG, "Can't unbind wallpaper service. "
+ "It might have crashed, just ignoring.");
}
mService = null;
}
if (mListener != null) {
mListener.onDisconnected();
}
}
/**
* @see ServiceConnection#onServiceConnected(ComponentName, IBinder)
*/
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IWallpaperService.Stub.asInterface(service);
try {
int displayId = mContainerView.getDisplay().getDisplayId();
mService.attach(this, mContainerView.getWindowToken(),
LayoutParams.TYPE_APPLICATION_MEDIA,
true, mContainerView.getWidth(), mContainerView.getHeight(),
new Rect(0, 0, 0, 0), displayId);
} catch (RemoteException e) {
Log.w(TAG, "Failed attaching wallpaper; clearing", e);
}
}
@Override
public void onLocalWallpaperColorsChanged(RectF area,
WallpaperColors colors, int displayId) {
}
/**
* @see ServiceConnection#onServiceDisconnected(ComponentName)
*/
public void onServiceDisconnected(ComponentName name) {
mService = null;
mEngine = null;
Log.w(TAG, "Wallpaper service gone: " + name);
}
/**
* @see IWallpaperConnection#attachEngine(IWallpaperEngine, int)
*/
public void attachEngine(IWallpaperEngine engine, int displayId) {
synchronized (this) {
if (mConnected) {
mEngine = engine;
if (mIsVisible) {
setEngineVisibility(true);
}
// Some wallpapers don't trigger #onWallpaperColorsChanged from remote. Requesting
// wallpaper color here to ensure the #onWallpaperColorsChanged would get called.
try {
mEngine.requestWallpaperColors();
} catch (RemoteException e) {
Log.w(TAG, "Failed requesting wallpaper colors", e);
}
} else {
try {
engine.destroy();
} catch (RemoteException e) {
// Ignore
}
}
}
}
/**
* Returns the engine handled by this WallpaperConnection
*/
@Nullable
public IWallpaperEngine getEngine() {
return mEngine;
}
/**
* @see IWallpaperConnection#setWallpaper(String)
*/
public ParcelFileDescriptor setWallpaper(String name) {
return null;
}
@Override
public void onWallpaperColorsChanged(WallpaperColors colors, int displayId) {
mContainerView.post(() -> {
if (mListener != null) {
mListener.onWallpaperColorsChanged(colors, displayId);
}
});
}
@Override
public void engineShown(IWallpaperEngine engine) {
mEngineReady = true;
if (mContainerView != null) {
mContainerView.post(() -> reparentWallpaperSurface(mContainerView));
}
if (mSecondContainerView != null) {
mSecondContainerView.post(() -> reparentWallpaperSurface(mSecondContainerView));
}
mContainerView.post(() -> {
if (mListener != null) {
mListener.onEngineShown();
}
});
}
/**
* Returns true if the wallpaper engine has been initialized.
*/
public boolean isEngineReady() {
return mEngineReady;
}
/**
* Sets the engine's visibility.
*/
public void setVisibility(boolean visible) {
mIsVisible = visible;
setEngineVisibility(visible);
}
private void setEngineVisibility(boolean visible) {
if (mEngine != null && visible != mIsEngineVisible) {
try {
mEngine.setVisibility(visible);
mIsEngineVisible = visible;
} catch (RemoteException e) {
Log.w(TAG, "Failure setting wallpaper visibility ", e);
}
}
}
private void reparentWallpaperSurface(SurfaceView parentSurface) {
if (mEngine == null) {
Log.i(TAG, "Engine is null, was the service disconnected?");
return;
}
if (parentSurface.getSurfaceControl() != null) {
mirrorAndReparent(parentSurface);
} else {
Log.d(TAG, "SurfaceView not initialized yet, adding callback");
parentSurface.getHolder().addCallback(new Callback() {
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mirrorAndReparent(parentSurface);
parentSurface.getHolder().removeCallback(this);
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
}
});
}
}
private void mirrorAndReparent(SurfaceView parentSurface) {
if (mEngine == null) {
Log.i(TAG, "Engine is null, was the service disconnected?");
return;
}
try {
SurfaceControl parentSC = parentSurface.getSurfaceControl();
SurfaceControl wallpaperMirrorSC = mEngine.mirrorSurfaceControl();
if (wallpaperMirrorSC == null) {
return;
}
float[] values = getScale(parentSurface);
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
t.setMatrix(wallpaperMirrorSC, values[MSCALE_X], values[MSKEW_Y],
values[MSKEW_X], values[MSCALE_Y]);
t.reparent(wallpaperMirrorSC, parentSC);
t.show(wallpaperMirrorSC);
t.apply();
} catch (RemoteException e) {
Log.e(TAG, "Couldn't reparent wallpaper surface", e);
}
}
private float[] getScale(SurfaceView parentSurface) {
Matrix m = new Matrix();
float[] values = new float[9];
Rect surfacePosition = parentSurface.getHolder().getSurfaceFrame();
DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics(
mContainerView.getResources(), mContainerView.getDisplay());
m.postScale(((float) surfacePosition.width()) / metrics.widthPixels,
((float) surfacePosition.height()) / metrics.heightPixels);
m.getValues(values);
return values;
}
/**
* Interface to be notified of connect/disconnect events from {@link WallpaperConnection}
*/
public interface WallpaperConnectionListener {
/**
* Called after the Wallpaper service has been bound.
*/
default void onConnected() {}
/**
* Called after the Wallpaper engine has been terminated and the service has been unbound.
*/
default void onDisconnected() {}
/**
* Called after the wallpaper has been rendered for the first time.
*/
default void onEngineShown() {}
/**
* Called after the wallpaper color is available or updated.
*/
default void onWallpaperColorsChanged(WallpaperColors colors, int displayId) {}
}
}