/* * Copyright (C) 2022 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.wm.shell.onehanded; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE; import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING; import android.content.Context; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Binder; import android.util.Slog; import android.view.ContextThemeWrapper; import android.view.IWindow; import android.view.LayoutInflater; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.SurfaceSession; import android.view.View; import android.view.WindowManager; import android.view.WindowlessWindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import java.io.PrintWriter; /** * Holds view hierarchy of a root surface and helps inflate a themeable view for background. */ public final class BackgroundWindowManager extends WindowlessWindowManager { private static final String TAG = BackgroundWindowManager.class.getSimpleName(); private static final int THEME_COLOR_OFFSET = 10; private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory mTransactionFactory; private Context mContext; private Rect mDisplayBounds; private SurfaceControlViewHost mViewHost; private SurfaceControl mLeash; private View mBackgroundView; private @OneHandedState.State int mCurrentState; public BackgroundWindowManager(Context context) { super(context.getResources().getConfiguration(), null /* rootSurface */, null /* hostInputToken */); mContext = context; mTransactionFactory = SurfaceControl.Transaction::new; } @Override public SurfaceControl getSurfaceControl(IWindow window) { return super.getSurfaceControl(window); } @Override public void setConfiguration(Configuration configuration) { super.setConfiguration(configuration); mContext = mContext.createConfigurationContext(configuration); } /** * onConfigurationChanged events for updating background theme color. */ public void onConfigurationChanged() { if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) { updateThemeOnly(); } } /** * One-handed mode state changed callback * @param newState of One-handed mode representing by {@link OneHandedState} */ public void onStateChanged(int newState) { mCurrentState = newState; } @Override protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) .setColorLayer() .setBufferSize(mDisplayBounds.width(), mDisplayBounds.height()) .setFormat(PixelFormat.RGB_888) .setOpaque(true) .setName(TAG) .setCallsite("BackgroundWindowManager#attachToParentSurface"); mLeash = builder.build(); b.setParent(mLeash); } /** Inflates background view on to the root surface. */ boolean initView() { if (mBackgroundView != null || mViewHost != null) { return false; } mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); mBackgroundView = (View) LayoutInflater.from(mContext) .inflate(R.layout.background_panel, null /* root */); WindowManager.LayoutParams lp = new WindowManager.LayoutParams( mDisplayBounds.width(), mDisplayBounds.height(), 0 /* TYPE NONE */, FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SLIPPERY, PixelFormat.TRANSLUCENT); lp.token = new Binder(); lp.setTitle("background-panel"); lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; mBackgroundView.setBackgroundColor(getThemeColorForBackground()); mViewHost.setView(mBackgroundView, lp); return true; } /** * Called when onDisplayAdded() or onDisplayRemoved() callback. * @param displayLayout The latest {@link DisplayLayout} for display bounds. */ public void onDisplayChanged(DisplayLayout displayLayout) { mDisplayBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height()); } private void updateThemeOnly() { if (mBackgroundView == null || mViewHost == null || mLeash == null) { Slog.w(TAG, "Background view or SurfaceControl does not exist when trying to " + "update theme only!"); return; } WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mBackgroundView.getLayoutParams(); mBackgroundView.setBackgroundColor(getThemeColorForBackground()); mViewHost.setView(mBackgroundView, lp); } /** * Shows the background layer when One-handed mode triggered. */ public void showBackgroundLayer() { if (!initView()) { updateThemeOnly(); return; } if (mLeash == null) { Slog.w(TAG, "SurfaceControl mLeash is null, can't show One-handed mode " + "background panel!"); return; } mTransactionFactory.getTransaction() .setAlpha(mLeash, 1.0f) .setLayer(mLeash, -1 /* at bottom-most layer */) .show(mLeash) .apply(); } /** * Remove the leash of background layer after stop One-handed mode. */ public void removeBackgroundLayer() { if (mBackgroundView != null) { mBackgroundView = null; } if (mViewHost != null) { mViewHost.release(); mViewHost = null; } if (mLeash != null) { mTransactionFactory.getTransaction().remove(mLeash).apply(); mLeash = null; } } /** * Gets {@link SurfaceControl} of the background layer. * @return {@code null} if not exist. */ @Nullable SurfaceControl getSurfaceControl() { return mLeash; } private int getThemeColor() { final Context themedContext = new ContextThemeWrapper(mContext, com.android.internal.R.style.Theme_DeviceDefault_DayNight); return themedContext.getColor(R.color.one_handed_tutorial_background_color); } int getThemeColorForBackground() { final int origThemeColor = getThemeColor(); return android.graphics.Color.argb(Color.alpha(origThemeColor), Color.red(origThemeColor) - THEME_COLOR_OFFSET, Color.green(origThemeColor) - THEME_COLOR_OFFSET, Color.blue(origThemeColor) - THEME_COLOR_OFFSET); } private float adjustColor(int origColor) { return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f; } void dump(@NonNull PrintWriter pw) { final String innerPrefix = " "; pw.println(TAG); pw.print(innerPrefix + "mDisplayBounds="); pw.println(mDisplayBounds); pw.print(innerPrefix + "mViewHost="); pw.println(mViewHost); pw.print(innerPrefix + "mLeash="); pw.println(mLeash); pw.print(innerPrefix + "mBackgroundView="); pw.println(mBackgroundView); } }