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