blob: d3274706631b665c42ca796047f5e7f9252c4e04 [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.wm.shell.onehanded;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.animation.LinearInterpolator;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.view.ContextThemeWrapper;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Manages OneHanded color background layer areas.
* To avoid when turning the Dark theme on, users can not clearly identify
* the screen has entered one handed mode.
*/
public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
implements OneHandedAnimationCallback, OneHandedState.OnStateChangedListener {
private static final String TAG = "OneHandedBackgroundPanelOrganizer";
private static final int THEME_COLOR_OFFSET = 10;
private static final int ALPHA_ANIMATION_DURATION = 200;
private final Context mContext;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
mTransactionFactory;
private @OneHandedState.State int mCurrentState;
private ValueAnimator mAlphaAnimator;
private float mTranslationFraction;
private float[] mThemeColor;
/**
* The background to distinguish the boundary of translated windows and empty region when
* one handed mode triggered.
*/
private Rect mBkgBounds;
private Rect mStableInsets;
@Nullable
@VisibleForTesting
SurfaceControl mBackgroundSurface;
@Nullable
private SurfaceControl mParentLeash;
public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout,
OneHandedSettingsUtil settingsUtil, Executor executor) {
super(executor);
mContext = context;
mTranslationFraction = settingsUtil.getTranslationFraction(context);
mTransactionFactory = SurfaceControl.Transaction::new;
updateThemeColors();
}
@Override
public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
@NonNull SurfaceControl leash) {
mParentLeash = leash;
}
@Override
public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) {
final List<DisplayAreaAppearedInfo> displayAreaInfos;
displayAreaInfos = super.registerOrganizer(displayAreaFeature);
for (int i = 0; i < displayAreaInfos.size(); i++) {
final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
}
return displayAreaInfos;
}
@Override
public void unregisterOrganizer() {
super.unregisterOrganizer();
removeBackgroundPanelLayer();
mParentLeash = null;
}
@Override
public void onAnimationUpdate(SurfaceControl.Transaction tx, float xPos, float yPos) {
final int yTopPos = (mStableInsets.top - mBkgBounds.height()) + Math.round(yPos);
tx.setPosition(mBackgroundSurface, 0, yTopPos);
}
@Nullable
@VisibleForTesting
boolean isRegistered() {
return mParentLeash != null;
}
void createBackgroundSurface() {
mBackgroundSurface = new SurfaceControl.Builder(mSurfaceSession)
.setBufferSize(mBkgBounds.width(), mBkgBounds.height())
.setColorLayer()
.setFormat(PixelFormat.RGB_888)
.setOpaque(true)
.setName("one-handed-background-panel")
.setCallsite("OneHandedBackgroundPanelOrganizer")
.build();
// TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
mAlphaAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
mAlphaAnimator.setInterpolator(new LinearInterpolator());
mAlphaAnimator.setDuration(ALPHA_ANIMATION_DURATION);
mAlphaAnimator.addUpdateListener(
animator -> detachBackgroundFromParent(animator));
}
void detachBackgroundFromParent(ValueAnimator animator) {
if (mBackgroundSurface == null || mParentLeash == null) {
return;
}
// TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
final float currentValue = (float) animator.getAnimatedValue();
final SurfaceControl.Transaction tx = mTransactionFactory.getTransaction();
if (currentValue == 0.0f) {
tx.reparent(mBackgroundSurface, null).apply();
} else {
tx.setAlpha(mBackgroundSurface, (float) animator.getAnimatedValue()).apply();
}
}
/**
* Called when onDisplayAdded() or onDisplayRemoved() callback.
*
* @param displayLayout The latest {@link DisplayLayout} representing current displayId
*/
public void onDisplayChanged(DisplayLayout displayLayout) {
mStableInsets = displayLayout.stableInsets();
// Ensure the mBkgBounds is portrait, due to OHM only support on portrait
if (displayLayout.height() > displayLayout.width()) {
mBkgBounds = new Rect(0, 0, displayLayout.width(),
Math.round(displayLayout.height() * mTranslationFraction) + mStableInsets.top);
} else {
mBkgBounds = new Rect(0, 0, displayLayout.height(),
Math.round(displayLayout.width() * mTranslationFraction) + mStableInsets.top);
}
}
@VisibleForTesting
void onStart() {
if (mBackgroundSurface == null) {
createBackgroundSurface();
}
showBackgroundPanelLayer();
}
/**
* Called when transition finished.
*/
public void onStopFinished() {
if (mAlphaAnimator == null) {
return;
}
mAlphaAnimator.start();
}
@VisibleForTesting
void showBackgroundPanelLayer() {
if (mParentLeash == null) {
return;
}
if (mBackgroundSurface == null) {
createBackgroundSurface();
}
// TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
if (mAlphaAnimator.isRunning()) {
mAlphaAnimator.end();
}
mTransactionFactory.getTransaction()
.reparent(mBackgroundSurface, mParentLeash)
.setAlpha(mBackgroundSurface, 1.0f)
.setLayer(mBackgroundSurface, -1 /* at bottom-most layer */)
.setColor(mBackgroundSurface, mThemeColor)
.show(mBackgroundSurface)
.apply();
}
@VisibleForTesting
void removeBackgroundPanelLayer() {
if (mBackgroundSurface == null) {
return;
}
mTransactionFactory.getTransaction()
.remove(mBackgroundSurface)
.apply();
mBackgroundSurface = null;
}
/**
* onConfigurationChanged events for updating tutorial text.
*/
public void onConfigurationChanged() {
updateThemeColors();
if (mCurrentState != STATE_ACTIVE) {
return;
}
showBackgroundPanelLayer();
}
private void updateThemeColors() {
final Context themedContext = new ContextThemeWrapper(mContext,
com.android.internal.R.style.Theme_DeviceDefault_DayNight);
final int themeColor = themedContext.getColor(
R.color.one_handed_tutorial_background_color);
mThemeColor = new float[]{
adjustColor(Color.red(themeColor)),
adjustColor(Color.green(themeColor)),
adjustColor(Color.blue(themeColor))};
}
private float adjustColor(int origColor) {
return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f;
}
@Override
public void onStateChanged(int newState) {
mCurrentState = newState;
}
void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG);
pw.print(innerPrefix + "mBackgroundSurface=");
pw.println(mBackgroundSurface);
pw.print(innerPrefix + "mBkgBounds=");
pw.println(mBkgBounds);
pw.print(innerPrefix + "mThemeColor=");
pw.println(mThemeColor);
pw.print(innerPrefix + "mTranslationFraction=");
pw.println(mTranslationFraction);
}
}