blob: 861f45f1efd6406550901cb3ab0f3cdcd27c67be [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.car.ui.core;
import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
import static com.android.car.ui.utils.CarUiUtils.getThemeBoolean;
import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
import android.annotation.TargetApi;
import android.app.Activity;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.android.car.ui.R;
import com.android.car.ui.baselayout.Insets;
import com.android.car.ui.baselayout.InsetsChangedListener;
import com.android.car.ui.pluginsupport.PluginFactorySingleton;
import com.android.car.ui.toolbar.ToolbarController;
import java.util.Map;
import java.util.WeakHashMap;
/**
* BaseLayoutController accepts an {@link Activity} and sets up the base layout inside of it.
* It also exposes a {@link ToolbarController} to access the toolbar. This may be null if
* used with a base layout without a Toolbar.
*/
@TargetApi(MIN_TARGET_API)
public final class BaseLayoutController {
private static final Map<Activity, BaseLayoutController> sBaseLayoutMap = new WeakHashMap<>();
private InsetsUpdater mInsetsUpdater;
/**
* Gets a BaseLayoutController for the given {@link Activity}. Must have called
* {@link #build(Activity)} with the same activity earlier, otherwise will return null.
*/
@Nullable
/* package */ static BaseLayoutController getBaseLayoutController(@Nullable Activity activity) {
return sBaseLayoutMap.get(activity);
}
@Nullable
private ToolbarController mToolbarController;
private BaseLayoutController(Activity activity) {
installBaseLayout(activity);
}
/**
* Create a new BaseLayoutController for the given {@link Activity}.
*
* <p>You can get a reference to it by calling {@link #getBaseLayoutController(Activity)}.
*/
/* package */
static void build(Activity activity) {
if (getThemeBoolean(activity, R.attr.carUiBaseLayout)) {
sBaseLayoutMap.put(activity, new BaseLayoutController(activity));
}
}
/**
* Destroy the BaseLayoutController for the given {@link Activity}.
*/
/* package */
static void destroy(Activity activity) {
sBaseLayoutMap.remove(activity);
}
/**
* Gets the {@link ToolbarController} for activities created with carUiBaseLayout and
* carUiToolbar set to true.
*/
@Nullable
/* package */ ToolbarController getToolbarController() {
return mToolbarController;
}
/* package */ Insets getInsets() {
return mInsetsUpdater.getInsets();
}
/* package */ void dispatchNewInsets(Insets insets) {
mInsetsUpdater.onCarUiInsetsChanged(insets);
}
/* package */ void replaceInsetsChangedListenerWith(InsetsChangedListener listener) {
mInsetsUpdater.replaceInsetsChangedListenerWith(listener);
}
/**
* Installs the base layout into an activity, moving its content view under the base layout.
*
* <p>This function must be called during the onCreate() of the {@link Activity}.
*
* @param activity The {@link Activity} to install a base layout in.
*/
private void installBaseLayout(Activity activity) {
boolean toolbarEnabled = getThemeBoolean(activity, R.attr.carUiToolbar);
View contentView =
requireViewByRefId(activity.getWindow().getDecorView(), android.R.id.content);
mInsetsUpdater = new InsetsUpdater(activity, contentView);
mToolbarController = PluginFactorySingleton.get(activity)
.installBaseLayoutAround(
contentView,
mInsetsUpdater,
toolbarEnabled,
true);
}
/**
* InsetsUpdater waits for layout changes, and when there is one, calculates the appropriate
* insets into the content view.
*
* <p>It then calls {@link InsetsChangedListener#onCarUiInsetsChanged(Insets)} on the
* {@link Activity} and any {@link Fragment Fragments} the Activity might have. If
* none of the Activity/Fragments implement {@link InsetsChangedListener}, it will set
* padding on the content view equal to the insets.
*/
public static final class InsetsUpdater implements InsetsChangedListener {
private InsetsChangedListener mInsetsChangedListenerDelegate;
@Nullable
private Activity mActivity;
private View mContentView;
@NonNull
private Insets mInsets = new Insets();
/**
* Constructs an InsetsUpdater that calculates and dispatches insets to an {@link Activity}.
*
* @param activity The activity that is using base layouts. Used to dispatch insets to if
* it implements {@link InsetsChangedListener}
* @param contentView The android.R.id.content View
*/
InsetsUpdater(
@Nullable Activity activity,
@NonNull View contentView) {
mActivity = activity;
mContentView = contentView;
}
@NonNull
Insets getInsets() {
return mInsets;
}
public void replaceInsetsChangedListenerWith(InsetsChangedListener listener) {
mInsetsChangedListenerDelegate = listener;
}
@Override
public void onCarUiInsetsChanged(@NonNull Insets insets) {
if (mInsets.equals(insets)) {
return;
}
mInsets = insets;
boolean handled = false;
if (mInsetsChangedListenerDelegate != null) {
mInsetsChangedListenerDelegate.onCarUiInsetsChanged(insets);
handled = true;
} else {
// If an explicit InsetsChangedListener is not provided,
// pass the insets to activities and fragments
if (mActivity instanceof InsetsChangedListener) {
((InsetsChangedListener) mActivity).onCarUiInsetsChanged(insets);
handled = true;
}
if (mActivity instanceof FragmentActivity) {
for (Fragment fragment : ((FragmentActivity) mActivity)
.getSupportFragmentManager().getFragments()) {
if (fragment instanceof InsetsChangedListener) {
((InsetsChangedListener) fragment).onCarUiInsetsChanged(insets);
handled = true;
}
}
}
}
if (!handled) {
mContentView.setPadding(insets.getLeft(), insets.getTop(),
insets.getRight(), insets.getBottom());
}
}
}
}