| /* |
| * 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.annotation.IntDef; |
| import android.content.res.Resources; |
| import android.graphics.PixelFormat; |
| import android.graphics.Region; |
| import android.util.Log; |
| import android.view.IWindow; |
| import android.view.SurfaceControlViewHost; |
| import android.view.SurfaceHolder; |
| import android.view.SurfaceView; |
| import android.view.View; |
| |
| import androidx.annotation.NonNull; |
| |
| import com.android.systemui.R; |
| import com.android.systemui.communal.CommunalStateController; |
| import com.android.systemui.statusbar.NotificationShadeWindowController; |
| import com.android.systemui.util.Utils; |
| import com.android.systemui.util.ViewController; |
| |
| import com.google.common.util.concurrent.ListenableFuture; |
| |
| import java.util.Optional; |
| import java.util.concurrent.Executor; |
| |
| /** |
| * {@link CommunalSurfaceViewController} coordinates requesting communal surfaces to populate a |
| * {@link SurfaceView} with. |
| */ |
| public class CommunalSurfaceViewController extends ViewController<SurfaceView> { |
| private static final String TAG = "CommunalSurfaceViewCtlr"; |
| private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); |
| private final Executor mMainExecutor; |
| private final CommunalStateController mCommunalStateController; |
| private final NotificationShadeWindowController mNotificationShadeWindowController; |
| private final CommunalSourceImpl mSource; |
| private final Resources mResources; |
| private final Region mSurfaceViewTouchableRegion; |
| |
| @IntDef({STATE_SURFACE_CREATED, STATE_SURFACE_VIEW_ATTACHED}) |
| private @interface State {} |
| |
| private static final int STATE_SURFACE_CREATED = 1 << 0; |
| private static final int STATE_SURFACE_VIEW_ATTACHED = 1 << 1; |
| |
| private static final int STATE_CAN_SHOW_SURFACE = |
| STATE_SURFACE_CREATED | STATE_SURFACE_VIEW_ATTACHED; |
| |
| private int mCurrentState; |
| |
| private Optional<CommunalSourceImpl.Request> mLastRequest = Optional.empty(); |
| |
| // The current in-flight request for a surface package. |
| private ListenableFuture<SurfaceControlViewHost.SurfacePackage> mCurrentSurfaceFuture; |
| |
| private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { |
| @Override |
| public void surfaceCreated(@NonNull SurfaceHolder holder) { |
| setState(STATE_SURFACE_CREATED, true); |
| } |
| |
| @Override |
| public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, |
| int height) { |
| } |
| |
| @Override |
| public void surfaceDestroyed(@NonNull SurfaceHolder holder) { |
| setState(STATE_SURFACE_CREATED, false); |
| } |
| }; |
| |
| private final View.OnLayoutChangeListener mOnLayoutChangeListener = |
| new View.OnLayoutChangeListener() { |
| @Override |
| public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, |
| int oldTop, int oldRight, int oldBottom) { |
| // The margin for the status bar and keyguard indication are excluded from the tap |
| // exclusion to preserve vertical swipes in this region. |
| final int topMargin = mResources.getDimensionPixelSize( |
| Utils.shouldUseSplitNotificationShade(mResources) |
| ? R.dimen.split_shade_header_height |
| : R.dimen.notification_panel_margin_top); |
| final int bottomMargin = mResources.getDimensionPixelSize( |
| R.dimen.keyguard_indication_bottom_padding); |
| |
| mSurfaceViewTouchableRegion.set(left, top + topMargin, right, bottom - bottomMargin); |
| updateTouchExclusion(); |
| |
| // Trigger showing (or hiding) surface based on new dimensions. |
| showSurface(); |
| } |
| }; |
| |
| private CommunalStateController.Callback mCommunalStateCallback = |
| new CommunalStateController.Callback() { |
| @Override |
| public void onCommunalViewOccludedChanged() { |
| updateTouchExclusion(); |
| } |
| }; |
| |
| protected CommunalSurfaceViewController(SurfaceView view, Resources resources, |
| Executor executor, CommunalStateController communalStateController, |
| NotificationShadeWindowController notificationShadeWindowController, |
| CommunalSourceImpl source) { |
| super(view); |
| mCommunalStateController = communalStateController; |
| mSource = source; |
| mResources = resources; |
| mMainExecutor = executor; |
| mNotificationShadeWindowController = notificationShadeWindowController; |
| mSurfaceViewTouchableRegion = new Region(); |
| } |
| |
| @Override |
| protected void onInit() { |
| mView.getHolder().setFormat(PixelFormat.TRANSPARENT); |
| mView.getHolder().addCallback(mSurfaceHolderCallback); |
| mView.addOnLayoutChangeListener(mOnLayoutChangeListener); |
| } |
| |
| private void setState(@State int state, boolean enabled) { |
| if (DEBUG) { |
| Log.d(TAG, "setState. state:" + state + " enable:" + enabled); |
| } |
| |
| final int newState = enabled ? mCurrentState | state : mCurrentState & ~state; |
| |
| // no new state is available |
| if (newState == mCurrentState) { |
| return; |
| } |
| |
| if (DEBUG) { |
| Log.d(TAG, "setState. new state:" + mCurrentState); |
| } |
| |
| mCurrentState = newState; |
| |
| showSurface(); |
| |
| updateTouchExclusion(); |
| } |
| |
| private void updateTouchExclusion() { |
| final IWindow window = IWindow.Stub.asInterface(mView.getWindowToken()); |
| final boolean excludeTouches = (mCurrentState & STATE_SURFACE_VIEW_ATTACHED) != 0 |
| && !mCommunalStateController.getCommunalViewOccluded(); |
| if (excludeTouches) { |
| mNotificationShadeWindowController.setTouchExclusionRegion(mSurfaceViewTouchableRegion); |
| } else { |
| final Region emptyRegion = Region.obtain(); |
| mNotificationShadeWindowController.setTouchExclusionRegion(emptyRegion); |
| emptyRegion.recycle(); |
| } |
| } |
| |
| private void showSurface() { |
| mView.setWillNotDraw(false); |
| |
| if (mCurrentState != STATE_CAN_SHOW_SURFACE) { |
| // If the surface is no longer showing, cancel any in-flight requests. |
| if (mCurrentSurfaceFuture != null) { |
| mCurrentSurfaceFuture.cancel(true); |
| mCurrentSurfaceFuture = null; |
| } |
| |
| mLastRequest = Optional.empty(); |
| mView.setWillNotDraw(true); |
| return; |
| } |
| |
| final CommunalSourceImpl.Request request = new CommunalSourceImpl.Request( |
| mView.getMeasuredWidth(), mView.getMeasuredHeight(), |
| mView.getDisplay().getDisplayId(), mView.getHostToken()); |
| |
| if (mLastRequest.isPresent() && mLastRequest.get().equals(request)) { |
| return; |
| } |
| |
| mLastRequest = Optional.of(request); |
| |
| // Since this method is only called when the state has changed, mCurrentSurfaceFuture should |
| // be null here. |
| mCurrentSurfaceFuture = mSource.requestCommunalSurface(request); |
| |
| mCurrentSurfaceFuture.addListener(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| // If the request is received after detached, ignore. |
| if (!mView.isAttachedToWindow()) { |
| return; |
| } |
| |
| SurfaceControlViewHost.SurfacePackage surfacePackage = |
| mCurrentSurfaceFuture.get(); |
| mCurrentSurfaceFuture = null; |
| |
| if (DEBUG) { |
| Log.d(TAG, "Received surface package:" + surfacePackage); |
| } |
| |
| if (surfacePackage != null) { |
| mView.setChildSurfacePackage(surfacePackage); |
| mView.postInvalidate(); |
| mCommunalStateController.setCommunalViewShowing(true); |
| } else { |
| Log.e(TAG, "couldn't get the surface package"); |
| } |
| } catch (Exception e) { |
| Log.e(TAG, "An error occurred retrieving the future result:" + e); |
| } |
| } |
| }, mMainExecutor); |
| } |
| |
| @Override |
| protected void onViewAttached() { |
| setState(STATE_SURFACE_VIEW_ATTACHED, true); |
| mCommunalStateController.addCallback(mCommunalStateCallback); |
| } |
| |
| @Override |
| protected void onViewDetached() { |
| mCommunalStateController.removeCallback(mCommunalStateCallback); |
| setState(STATE_SURFACE_VIEW_ATTACHED, false); |
| } |
| } |