1 /** 2 * Copyright (C) 2018 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.settings.core; 17 18 import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL_FAST; 19 20 import android.annotation.LayoutRes; 21 import android.app.ActivityManager; 22 import android.content.ComponentName; 23 import android.content.Intent; 24 import android.content.pm.PackageManager; 25 import android.content.res.Configuration; 26 import android.content.res.TypedArray; 27 import android.graphics.text.LineBreakConfig; 28 import android.os.Bundle; 29 import android.text.TextUtils; 30 import android.util.Log; 31 import android.view.LayoutInflater; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.Window; 35 import android.widget.Toolbar; 36 37 import androidx.annotation.NonNull; 38 import androidx.annotation.Nullable; 39 import androidx.coordinatorlayout.widget.CoordinatorLayout; 40 import androidx.fragment.app.FragmentActivity; 41 42 import com.android.settings.R; 43 import com.android.settings.SubSettings; 44 import com.android.settings.Utils; 45 import com.android.settings.core.CategoryMixin.CategoryHandler; 46 import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; 47 import com.android.settingslib.transition.SettingsTransitionHelper.TransitionType; 48 import com.android.settingslib.widget.SettingsThemeHelper; 49 import com.android.window.flags.Flags; 50 51 import com.google.android.material.appbar.AppBarLayout; 52 import com.google.android.material.appbar.CollapsingToolbarLayout; 53 import com.google.android.material.resources.TextAppearanceConfig; 54 import com.google.android.setupcompat.util.WizardManagerHelper; 55 import com.google.android.setupdesign.transition.TransitionHelper; 56 import com.google.android.setupdesign.util.ThemeHelper; 57 58 /** Base activity for Settings pages */ 59 public class SettingsBaseActivity extends FragmentActivity implements CategoryHandler { 60 61 /** 62 * What type of page transition should be apply. 63 */ 64 public static final String EXTRA_PAGE_TRANSITION_TYPE = "page_transition_type"; 65 66 protected static final boolean DEBUG_TIMING = false; 67 private static final String TAG = "SettingsBaseActivity"; 68 private static final int DEFAULT_REQUEST = -1; 69 private static final float TOOLBAR_LINE_SPACING_MULTIPLIER = 1.1f; 70 71 private static final int EXPRESSIVE_LAYOUT_ID = 72 com.android.settingslib.collapsingtoolbar.R.layout.settingslib_expressive_collapsing_toolbar_base_layout; 73 private static final int COLLAPSING_LAYOUT_ID = 74 com.android.settingslib.collapsingtoolbar.R.layout.collapsing_toolbar_base_layout; 75 76 77 protected CategoryMixin mCategoryMixin; 78 protected CollapsingToolbarLayout mCollapsingToolbarLayout; 79 protected AppBarLayout mAppBarLayout; 80 private Toolbar mToolbar; 81 82 @Override getCategoryMixin()83 public CategoryMixin getCategoryMixin() { 84 return mCategoryMixin; 85 } 86 87 @Override onCreate(@ullable Bundle savedInstanceState)88 protected void onCreate(@Nullable Bundle savedInstanceState) { 89 final boolean isAnySetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); 90 if (isAnySetupWizard) { 91 TransitionHelper.applyForwardTransition(this); 92 TransitionHelper.applyBackwardTransition(this); 93 } 94 super.onCreate(savedInstanceState); 95 if (isFinishing()) { 96 return; 97 } 98 if (isLockTaskModePinned() && !isSettingsRunOnTop()) { 99 Log.w(TAG, "Devices lock task mode pinned."); 100 finish(); 101 } 102 final long startTime = System.currentTimeMillis(); 103 if (Flags.enforceEdgeToEdge()) { 104 Utils.setupEdgeToEdge(this); 105 hideInternalActionBar(); 106 } 107 getLifecycle().addObserver(new HideNonSystemOverlayMixin(this)); 108 TextAppearanceConfig.setShouldLoadFontSynchronously(true); 109 110 mCategoryMixin = new CategoryMixin(this); 111 getLifecycle().addObserver(mCategoryMixin); 112 113 final TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme); 114 if (!theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) { 115 requestWindowFeature(Window.FEATURE_NO_TITLE); 116 } 117 // Apply SetupWizard light theme during setup flow. This is for SubSettings pages. 118 if (isAnySetupWizard && this instanceof SubSettings) { 119 setTheme(R.style.SettingsPreferenceTheme_SetupWizard); 120 ThemeHelper.trySetSuwTheme(this); 121 } 122 123 if (isToolbarEnabled() && !isAnySetupWizard) { 124 int resId = SettingsThemeHelper.isExpressiveTheme(getApplicationContext()) 125 ? EXPRESSIVE_LAYOUT_ID : COLLAPSING_LAYOUT_ID; 126 super.setContentView(resId); 127 mCollapsingToolbarLayout = 128 findViewById(com.android.settingslib.collapsingtoolbar.R.id.collapsing_toolbar); 129 mAppBarLayout = findViewById(R.id.app_bar); 130 if (mCollapsingToolbarLayout != null) { 131 mCollapsingToolbarLayout.setLineSpacingMultiplier(TOOLBAR_LINE_SPACING_MULTIPLIER); 132 mCollapsingToolbarLayout.setHyphenationFrequency(HYPHENATION_FREQUENCY_NORMAL_FAST); 133 mCollapsingToolbarLayout.setStaticLayoutBuilderConfigurer(builder -> 134 builder.setLineBreakConfig( 135 new LineBreakConfig.Builder() 136 .setLineBreakWordStyle( 137 LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) 138 .build())); 139 } 140 autoSetCollapsingToolbarLayoutScrolling(); 141 } else { 142 super.setContentView(R.layout.settings_base_layout); 143 } 144 145 // This is to hide the toolbar from those pages which don't need a toolbar originally. 146 final Toolbar toolbar = findViewById(R.id.action_bar); 147 if (!isToolbarEnabled() || isAnySetupWizard) { 148 toolbar.setVisibility(View.GONE); 149 return; 150 } 151 setActionBar(toolbar); 152 153 if (DEBUG_TIMING) { 154 Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms"); 155 } 156 } 157 158 @Override setActionBar(@ndroidx.annotation.Nullable Toolbar toolbar)159 public void setActionBar(@androidx.annotation.Nullable Toolbar toolbar) { 160 super.setActionBar(toolbar); 161 162 mToolbar = toolbar; 163 } 164 165 @Override onNavigateUp()166 public boolean onNavigateUp() { 167 if (!super.onNavigateUp()) { 168 finishAfterTransition(); 169 } 170 return true; 171 } 172 173 @Override startActivityForResult(Intent intent, int requestCode, @androidx.annotation.Nullable Bundle options)174 public void startActivityForResult(Intent intent, int requestCode, 175 @androidx.annotation.Nullable Bundle options) { 176 final int transitionType = getTransitionType(intent); 177 super.startActivityForResult(intent, requestCode, options); 178 if (transitionType == TransitionType.TRANSITION_SLIDE) { 179 overridePendingTransition( 180 com.google.android.setupdesign.R.anim.sud_slide_next_in, 181 com.google.android.setupdesign.R.anim.sud_slide_next_out); 182 } else if (transitionType == TransitionType.TRANSITION_FADE) { 183 overridePendingTransition( 184 android.R.anim.fade_in, com.google.android.setupdesign.R.anim.sud_stay); 185 } 186 } 187 188 @Override onPause()189 protected void onPause() { 190 // For accessibility activities launched from setup wizard. 191 if (getTransitionType(getIntent()) == TransitionType.TRANSITION_FADE) { 192 overridePendingTransition( 193 com.google.android.setupdesign.R.anim.sud_stay, android.R.anim.fade_out); 194 } 195 super.onPause(); 196 } 197 198 @Override setContentView(@ayoutRes int layoutResID)199 public void setContentView(@LayoutRes int layoutResID) { 200 final ViewGroup parent = findViewById(R.id.content_frame); 201 if (parent != null) { 202 parent.removeAllViews(); 203 } 204 LayoutInflater.from(this).inflate(layoutResID, parent); 205 } 206 207 @Override setContentView(View view)208 public void setContentView(View view) { 209 ((ViewGroup) findViewById(R.id.content_frame)).addView(view); 210 } 211 212 @Override setContentView(View view, ViewGroup.LayoutParams params)213 public void setContentView(View view, ViewGroup.LayoutParams params) { 214 ((ViewGroup) findViewById(R.id.content_frame)).addView(view, params); 215 } 216 217 @Override setTitle(CharSequence title)218 public void setTitle(CharSequence title) { 219 super.setTitle(title); 220 if (mCollapsingToolbarLayout != null) { 221 mCollapsingToolbarLayout.setTitle(title); 222 } 223 } 224 225 @Override setTitle(int titleId)226 public void setTitle(int titleId) { 227 super.setTitle(getText(titleId)); 228 if (mCollapsingToolbarLayout != null) { 229 mCollapsingToolbarLayout.setTitle(getText(titleId)); 230 } 231 } 232 233 /** 234 * SubSetting page should show a toolbar by default. If the page wouldn't show a toolbar, 235 * override this method and return false value. 236 * 237 * @return ture by default 238 */ isToolbarEnabled()239 protected boolean isToolbarEnabled() { 240 return true; 241 } 242 isLockTaskModePinned()243 private boolean isLockTaskModePinned() { 244 final ActivityManager activityManager = 245 getApplicationContext().getSystemService(ActivityManager.class); 246 return activityManager.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED; 247 } 248 isSettingsRunOnTop()249 private boolean isSettingsRunOnTop() { 250 final ActivityManager activityManager = 251 getApplicationContext().getSystemService(ActivityManager.class); 252 final String taskPkgName = activityManager.getRunningTasks(1 /* maxNum */) 253 .get(0 /* index */).baseActivity.getPackageName(); 254 return TextUtils.equals(getPackageName(), taskPkgName); 255 } 256 257 /** 258 * @return whether or not the enabled state actually changed. 259 */ setTileEnabled(ComponentName component, boolean enabled)260 public boolean setTileEnabled(ComponentName component, boolean enabled) { 261 final PackageManager pm = getPackageManager(); 262 int state = pm.getComponentEnabledSetting(component); 263 boolean isEnabled = state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; 264 if (isEnabled != enabled || state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { 265 if (enabled) { 266 mCategoryMixin.removeFromDenylist(component); 267 } else { 268 mCategoryMixin.addToDenylist(component); 269 } 270 pm.setComponentEnabledSetting(component, enabled 271 ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED 272 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 273 PackageManager.DONT_KILL_APP); 274 return true; 275 } 276 return false; 277 } 278 autoSetCollapsingToolbarLayoutScrolling()279 private void autoSetCollapsingToolbarLayoutScrolling() { 280 if (mAppBarLayout == null) { 281 return; 282 } 283 final CoordinatorLayout.LayoutParams params = 284 (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams(); 285 final AppBarLayout.Behavior behavior = new AppBarLayout.Behavior(); 286 behavior.setDragCallback( 287 new AppBarLayout.Behavior.DragCallback() { 288 @Override 289 public boolean canDrag(@NonNull AppBarLayout appBarLayout) { 290 // Header can be scrolling while device in landscape mode. 291 return appBarLayout.getResources().getConfiguration().orientation 292 == Configuration.ORIENTATION_LANDSCAPE; 293 } 294 }); 295 params.setBehavior(behavior); 296 } 297 getTransitionType(Intent intent)298 private int getTransitionType(Intent intent) { 299 if (intent == null) { 300 return TransitionType.TRANSITION_NONE; 301 } 302 return intent.getIntExtra(EXTRA_PAGE_TRANSITION_TYPE, TransitionType.TRANSITION_NONE); 303 } 304 305 /** 306 * This internal ActionBar will be appeared automatically when the 307 * Utils.setupEdgeToEdge is invoked. 308 * 309 * @see Utils.setupEdgeToEdge 310 */ hideInternalActionBar()311 private void hideInternalActionBar() { 312 final View actionBarContainer = 313 findViewById(com.android.internal.R.id.action_bar_container); 314 if (actionBarContainer != null) { 315 actionBarContainer.setVisibility(View.GONE); 316 } 317 } 318 } 319