blob: b8070abd90b8313ce537c55003f7e2dace77f8ed [file] [log] [blame]
/*
* Copyright (C) 2021 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.systemui.communal.service;
import android.content.Context;
import android.content.res.Resources;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceView;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import com.android.systemui.communal.CommunalSource;
import com.android.systemui.communal.CommunalStateController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.communal.ICommunalSource;
import com.android.systemui.shared.communal.ICommunalSurfaceCallback;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.google.android.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/**
* {@link CommunalSourceImpl} provides a wrapper around {@link ICommunalSource} proxies as an
* implementation of {@link CommunalSource}. Requests and responses for communal surfaces are
* translated into the proper binder calls.
*/
public class CommunalSourceImpl implements CommunalSource {
private static final String TAG = "CommunalSourceImpl";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ICommunalSource mSourceProxy;
private final Resources mResources;
private final Executor mMainExecutor;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final CommunalStateController mCommunalStateController;
static class Factory {
private final Executor mExecutor;
private final Resources mResources;
private final CommunalStateController mCommunalStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
@Inject
Factory(@Main Executor executor, @Main Resources resources,
NotificationShadeWindowController notificationShadeWindowController,
CommunalStateController communalStateController) {
mExecutor = executor;
mResources = resources;
mNotificationShadeWindowController = notificationShadeWindowController;
mCommunalStateController = communalStateController;
}
public CommunalSource create(ICommunalSource source) {
return new CommunalSourceImpl(mExecutor, mResources, mCommunalStateController,
mNotificationShadeWindowController, source);
}
}
static class Request {
private final int mWidth;
private final int mHeight;
private final int mDisplayId;
private final IBinder mHostToken;
Request(int width, int height, int displayId, IBinder hostToken) {
mWidth = width;
mHeight = height;
mDisplayId = displayId;
mHostToken = hostToken;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Request)) return false;
Request request = (Request) o;
return mWidth == request.mWidth && mHeight == request.mHeight
&& mDisplayId == request.mDisplayId && Objects.equals(mHostToken,
request.mHostToken);
}
@Override
public int hashCode() {
return Objects.hash(mWidth, mHeight, mDisplayId, mHostToken);
}
@Override
public String toString() {
return "Request{"
+ "mWidth=" + mWidth
+ ", mHeight=" + mHeight
+ ", mDisplayId=" + mDisplayId
+ ", mHostToken=" + mHostToken
+ '}';
}
}
// mConnected is initialized to true as it is presumed instances are constructed with valid
// proxies. The source can never be reconnected once the proxy has died. Once this value
// becomes false, the source will always report disconnected to registering callbacks.
private boolean mConnected = true;
// A list of {@link Callback} that have registered to receive updates.
private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();
public CommunalSourceImpl(Executor mainExecutor, Resources resources,
CommunalStateController communalStateController,
NotificationShadeWindowController notificationShadeWindowController,
ICommunalSource sourceProxy) {
mMainExecutor = mainExecutor;
mCommunalStateController = communalStateController;
mNotificationShadeWindowController = notificationShadeWindowController;
mResources = resources;
mSourceProxy = sourceProxy;
try {
// Track connection status based on proxy lifetime.
mSourceProxy.asBinder().linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (DEBUG) {
Log.d(TAG, "Source lost. Clearing reporting disconnect.");
}
// Set connection state and inform callbacks.
onDisconnected();
}
}, 0);
} catch (RemoteException e) {
Log.e(TAG, "Could not link to the source proxy death:" + e);
}
}
private void onDisconnected() {
mConnected = false;
for (WeakReference<Callback> cbRef : mCallbacks) {
final Callback cb = cbRef.get();
if (cb != null) {
cb.onDisconnected();
}
}
mCallbacks.clear();
}
@Override
public ListenableFuture<CommunalViewResult> requestCommunalView(Context context) {
if (DEBUG) {
Log.d(TAG, "Received request for communal view");
}
ListenableFuture<CommunalViewResult> packageFuture =
CallbackToFutureAdapter.getFuture(completer -> {
final SurfaceView view = new SurfaceView(context);
completer.set(new CommunalViewResult(view,
new CommunalSurfaceViewController(view, mResources, mMainExecutor,
mCommunalStateController, mNotificationShadeWindowController,
this)));
return "CommunalSourceImpl::requestCommunalSurface::getCommunalSurface";
});
return packageFuture;
}
/**
* Called internally to request a new {@link android.view.SurfaceControlViewHost.SurfacePackage}
* for showing communal content.
*
* @param request A request with the parameters for the new communal surface.
* @return A future that returns the resulting
* {@link android.view.SurfaceControlViewHost.SurfacePackage}.
*/
protected ListenableFuture<SurfaceControlViewHost.SurfacePackage> requestCommunalSurface(
Request request) {
return CallbackToFutureAdapter.getFuture(completer -> {
mSourceProxy.getCommunalSurface(request.mHostToken, request.mWidth, request.mHeight,
request.mDisplayId, new ICommunalSurfaceCallback.Stub() {
@Override
public void onSurface(
SurfaceControlViewHost.SurfacePackage surfacePackage) {
completer.set(surfacePackage);
}
});
return "CommunalSourceImpl::requestCommunalSurface::getCommunalSurface";
});
}
@Override
public void addCallback(Callback callback) {
mCallbacks.add(new WeakReference<>(callback));
// If not connected anymore, immediately inform new callback of disconnection and remove.
if (!mConnected) {
onDisconnected();
}
}
@Override
public void removeCallback(Callback callback) {
mCallbacks.removeIf(el -> el.get() == callback);
}
}