| /* |
| * 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 SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) { |
| 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(); |
| return 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, |
| "BackgroundWindowManager"); |
| 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); |
| } |
| |
| } |