blob: 1fc4d12def1f5b43e916968677b86d1a67667334 [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.wm.shell.sizecompatui;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
/**
* Controls to show/update restart-activity buttons on Tasks based on whether the foreground
* activities are in size compatibility mode.
*/
public class SizeCompatUIController implements DisplayController.OnDisplaysChangedListener,
DisplayImeController.ImePositionProcessor {
/** Callback for size compat UI interaction. */
public interface SizeCompatUICallback {
/** Called when the size compat restart button is clicked. */
void onSizeCompatRestartButtonClicked(int taskId);
}
private static final String TAG = "SizeCompatUIController";
/** Whether the IME is shown on display id. */
private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1);
/** The showing UIs by task id. */
private final SparseArray<SizeCompatUILayout> mActiveLayouts = new SparseArray<>(0);
/** Avoid creating display context frequently for non-default display. */
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
private final Context mContext;
private final DisplayController mDisplayController;
private final DisplayImeController mImeController;
private final SyncTransactionQueue mSyncQueue;
private SizeCompatUICallback mCallback;
/** Only show once automatically in the process life. */
private boolean mHasShownHint;
public SizeCompatUIController(Context context,
DisplayController displayController,
DisplayImeController imeController,
SyncTransactionQueue syncQueue) {
mContext = context;
mDisplayController = displayController;
mImeController = imeController;
mSyncQueue = syncQueue;
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
}
/** Sets the callback for UI interactions. */
public void setSizeCompatUICallback(SizeCompatUICallback callback) {
mCallback = callback;
}
/**
* Called when the Task info changed. Creates and updates the size compat UI if there is an
* activity in size compat, or removes the UI if there is no size compat activity.
* @param displayId display the task and activity are in.
* @param taskId task the activity is in.
* @param taskConfig task config to place the size compat UI with.
* @param taskListener listener to handle the Task Surface placement.
*/
public void onSizeCompatInfoChanged(int displayId, int taskId,
@Nullable Configuration taskConfig,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (taskConfig == null || taskListener == null) {
// Null token means the current foreground activity is not in size compatibility mode.
removeLayout(taskId);
} else if (mActiveLayouts.contains(taskId)) {
// UI already exists, update the UI layout.
updateLayout(taskId, taskConfig, taskListener);
} else {
// Create a new size compat UI.
createLayout(displayId, taskId, taskConfig, taskListener);
}
}
@Override
public void onDisplayRemoved(int displayId) {
mDisplayContextCache.remove(displayId);
// Remove all size compat UIs on the removed display.
final List<Integer> toRemoveTaskIds = new ArrayList<>();
forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId()));
for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) {
removeLayout(toRemoveTaskIds.get(i));
}
}
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
forAllLayoutsOnDisplay(displayId, layout -> layout.updateDisplayLayout(displayLayout));
}
@Override
public void onImeVisibilityChanged(int displayId, boolean isShowing) {
if (isShowing) {
mDisplaysWithIme.add(displayId);
} else {
mDisplaysWithIme.remove(displayId);
}
// Hide the size compat UIs when input method is showing.
forAllLayoutsOnDisplay(displayId, layout -> layout.updateImeVisibility(isShowing));
}
private boolean isImeShowingOnDisplay(int displayId) {
return mDisplaysWithIme.contains(displayId);
}
private void createLayout(int displayId, int taskId, Configuration taskConfig,
ShellTaskOrganizer.TaskListener taskListener) {
final Context context = getOrCreateDisplayContext(displayId);
if (context == null) {
Log.e(TAG, "Cannot get context for display " + displayId);
return;
}
final SizeCompatUILayout layout = createLayout(context, displayId, taskId, taskConfig,
taskListener);
mActiveLayouts.put(taskId, layout);
layout.createSizeCompatButton(isImeShowingOnDisplay(displayId));
}
@VisibleForTesting
SizeCompatUILayout createLayout(Context context, int displayId, int taskId,
Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) {
final SizeCompatUILayout layout = new SizeCompatUILayout(mSyncQueue, mCallback, context,
taskConfig, taskId, taskListener, mDisplayController.getDisplayLayout(displayId),
mHasShownHint);
// Only show hint for the first time.
mHasShownHint = true;
return layout;
}
private void updateLayout(int taskId, Configuration taskConfig,
ShellTaskOrganizer.TaskListener taskListener) {
final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
if (layout == null) {
return;
}
layout.updateSizeCompatInfo(taskConfig, taskListener,
isImeShowingOnDisplay(layout.getDisplayId()));
}
private void removeLayout(int taskId) {
final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
if (layout != null) {
layout.release();
mActiveLayouts.remove(taskId);
}
}
private Context getOrCreateDisplayContext(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
return mContext;
}
Context context = null;
final WeakReference<Context> ref = mDisplayContextCache.get(displayId);
if (ref != null) {
context = ref.get();
}
if (context == null) {
Display display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
if (display != null) {
context = mContext.createDisplayContext(display);
mDisplayContextCache.put(displayId, new WeakReference<>(context));
}
}
return context;
}
private void forAllLayoutsOnDisplay(int displayId, Consumer<SizeCompatUILayout> callback) {
for (int i = 0; i < mActiveLayouts.size(); i++) {
final int taskId = mActiveLayouts.keyAt(i);
final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
if (layout != null && layout.getDisplayId() == displayId) {
callback.accept(layout);
}
}
}
}