1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.ui.core; 17 18 import static com.android.car.ui.core.CarUi.MIN_TARGET_API; 19 import static com.android.car.ui.utils.CarUiUtils.getThemeBoolean; 20 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId; 21 22 import android.annotation.TargetApi; 23 import android.app.Activity; 24 import android.view.View; 25 26 import androidx.annotation.NonNull; 27 import androidx.annotation.Nullable; 28 import androidx.fragment.app.Fragment; 29 import androidx.fragment.app.FragmentActivity; 30 31 import com.android.car.ui.R; 32 import com.android.car.ui.baselayout.Insets; 33 import com.android.car.ui.baselayout.InsetsChangedListener; 34 import com.android.car.ui.pluginsupport.PluginFactorySingleton; 35 import com.android.car.ui.toolbar.ToolbarController; 36 37 import java.util.Map; 38 import java.util.WeakHashMap; 39 40 /** 41 * BaseLayoutController accepts an {@link Activity} and sets up the base layout inside of it. 42 * It also exposes a {@link ToolbarController} to access the toolbar. This may be null if 43 * used with a base layout without a Toolbar. 44 */ 45 @TargetApi(MIN_TARGET_API) 46 public final class BaseLayoutController { 47 48 private static final Map<Activity, BaseLayoutController> sBaseLayoutMap = new WeakHashMap<>(); 49 50 private InsetsUpdater mInsetsUpdater; 51 52 /** 53 * Gets a BaseLayoutController for the given {@link Activity}. Must have called 54 * {@link #build(Activity)} with the same activity earlier, otherwise will return null. 55 */ 56 @Nullable getBaseLayoutController(@ullable Activity activity)57 /* package */ static BaseLayoutController getBaseLayoutController(@Nullable Activity activity) { 58 return sBaseLayoutMap.get(activity); 59 } 60 61 @Nullable 62 private ToolbarController mToolbarController; 63 BaseLayoutController(Activity activity)64 private BaseLayoutController(Activity activity) { 65 installBaseLayout(activity); 66 } 67 68 /** 69 * Create a new BaseLayoutController for the given {@link Activity}. 70 * 71 * <p>You can get a reference to it by calling {@link #getBaseLayoutController(Activity)}. 72 */ 73 /* package */ build(Activity activity)74 static void build(Activity activity) { 75 if (getThemeBoolean(activity, R.attr.carUiBaseLayout)) { 76 sBaseLayoutMap.put(activity, new BaseLayoutController(activity)); 77 } 78 } 79 80 /** 81 * Destroy the BaseLayoutController for the given {@link Activity}. 82 */ 83 /* package */ destroy(Activity activity)84 static void destroy(Activity activity) { 85 sBaseLayoutMap.remove(activity); 86 } 87 88 /** 89 * Gets the {@link ToolbarController} for activities created with carUiBaseLayout and 90 * carUiToolbar set to true. 91 */ 92 @Nullable getToolbarController()93 /* package */ ToolbarController getToolbarController() { 94 return mToolbarController; 95 } 96 getInsets()97 /* package */ Insets getInsets() { 98 return mInsetsUpdater.getInsets(); 99 } 100 dispatchNewInsets(Insets insets)101 /* package */ void dispatchNewInsets(Insets insets) { 102 mInsetsUpdater.onCarUiInsetsChanged(insets); 103 } 104 replaceInsetsChangedListenerWith(InsetsChangedListener listener)105 /* package */ void replaceInsetsChangedListenerWith(InsetsChangedListener listener) { 106 mInsetsUpdater.replaceInsetsChangedListenerWith(listener); 107 } 108 109 /** 110 * Installs the base layout into an activity, moving its content view under the base layout. 111 * 112 * <p>This function must be called during the onCreate() of the {@link Activity}. 113 * 114 * @param activity The {@link Activity} to install a base layout in. 115 */ installBaseLayout(Activity activity)116 private void installBaseLayout(Activity activity) { 117 boolean toolbarEnabled = getThemeBoolean(activity, R.attr.carUiToolbar); 118 119 View contentView = 120 requireViewByRefId(activity.getWindow().getDecorView(), android.R.id.content); 121 122 mInsetsUpdater = new InsetsUpdater(activity, contentView); 123 mToolbarController = PluginFactorySingleton.get(activity) 124 .installBaseLayoutAround( 125 contentView, 126 mInsetsUpdater, 127 toolbarEnabled, 128 true); 129 } 130 131 /** 132 * InsetsUpdater waits for layout changes, and when there is one, calculates the appropriate 133 * insets into the content view. 134 * 135 * <p>It then calls {@link InsetsChangedListener#onCarUiInsetsChanged(Insets)} on the 136 * {@link Activity} and any {@link Fragment Fragments} the Activity might have. If 137 * none of the Activity/Fragments implement {@link InsetsChangedListener}, it will set 138 * padding on the content view equal to the insets. 139 */ 140 public static final class InsetsUpdater implements InsetsChangedListener { 141 private InsetsChangedListener mInsetsChangedListenerDelegate; 142 143 @Nullable 144 private Activity mActivity; 145 private View mContentView; 146 147 @NonNull 148 private Insets mInsets = new Insets(); 149 150 /** 151 * Constructs an InsetsUpdater that calculates and dispatches insets to an {@link Activity}. 152 * 153 * @param activity The activity that is using base layouts. Used to dispatch insets to if 154 * it implements {@link InsetsChangedListener} 155 * @param contentView The android.R.id.content View 156 */ InsetsUpdater( @ullable Activity activity, @NonNull View contentView)157 InsetsUpdater( 158 @Nullable Activity activity, 159 @NonNull View contentView) { 160 mActivity = activity; 161 mContentView = contentView; 162 } 163 164 @NonNull getInsets()165 Insets getInsets() { 166 return mInsets; 167 } 168 replaceInsetsChangedListenerWith(InsetsChangedListener listener)169 public void replaceInsetsChangedListenerWith(InsetsChangedListener listener) { 170 mInsetsChangedListenerDelegate = listener; 171 } 172 173 @Override onCarUiInsetsChanged(@onNull Insets insets)174 public void onCarUiInsetsChanged(@NonNull Insets insets) { 175 if (mInsets.equals(insets)) { 176 return; 177 } 178 179 mInsets = insets; 180 181 boolean handled = false; 182 if (mInsetsChangedListenerDelegate != null) { 183 mInsetsChangedListenerDelegate.onCarUiInsetsChanged(insets); 184 handled = true; 185 } else { 186 // If an explicit InsetsChangedListener is not provided, 187 // pass the insets to activities and fragments 188 if (mActivity instanceof InsetsChangedListener) { 189 ((InsetsChangedListener) mActivity).onCarUiInsetsChanged(insets); 190 handled = true; 191 } 192 193 if (mActivity instanceof FragmentActivity) { 194 for (Fragment fragment : ((FragmentActivity) mActivity) 195 .getSupportFragmentManager().getFragments()) { 196 if (fragment instanceof InsetsChangedListener) { 197 ((InsetsChangedListener) fragment).onCarUiInsetsChanged(insets); 198 handled = true; 199 } 200 } 201 } 202 } 203 204 if (!handled) { 205 mContentView.setPadding(insets.getLeft(), insets.getTop(), 206 insets.getRight(), insets.getBottom()); 207 } 208 } 209 } 210 } 211