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