1 /*
2  * Copyright (C) 2016 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 
17 package com.android.tv.settings;
18 
19 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
20 
21 import static com.android.tv.settings.overlay.FlavorUtils.ALL_FLAVORS_MASK;
22 
23 import android.content.Intent;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.transition.Scene;
28 import android.transition.Slide;
29 import android.transition.Transition;
30 import android.transition.TransitionManager;
31 import android.util.Log;
32 import android.view.Gravity;
33 import android.view.ViewGroup;
34 import android.view.ViewTreeObserver;
35 
36 import androidx.annotation.Nullable;
37 import androidx.fragment.app.Fragment;
38 import androidx.fragment.app.FragmentActivity;
39 import androidx.fragment.app.FragmentManager;
40 
41 import com.android.tv.settings.overlay.FlavorUtils;
42 
43 import java.util.List;
44 
45 public abstract class TvSettingsActivity extends FragmentActivity {
46     private static final String TAG = "TvSettingsActivity";
47 
48     private static final String SETTINGS_FRAGMENT_TAG =
49             "com.android.tv.settings.MainSettings.SETTINGS_FRAGMENT";
50 
51     private static final int UI_UPDATE_DELAY_MS = 200;
52     private static final int REQUEST_CODE_STARTUP_VERIFICATION = 1;
53 
54     private Handler mHandler = new Handler(Looper.getMainLooper());
55 
getVisibleFragment()56     public Fragment getVisibleFragment() {
57         FragmentManager fragmentManager = getSupportFragmentManager();
58         List<Fragment> fragments = fragmentManager.getFragments();
59         if (fragments != null) {
60             for (Fragment fragment : fragments) {
61                 if (fragment != null && fragment.isVisible()) {
62                     return fragment;
63                 }
64             }
65         }
66         return null;
67     }
68 
69     @Override
onCreate(@ullable Bundle savedInstanceState)70     protected void onCreate(@Nullable Bundle savedInstanceState) {
71         super.onCreate(savedInstanceState);
72 
73         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
74 
75         if ((FlavorUtils.getFlavor(this) & getAvailableFlavors()) == 0) {
76             Log.w(TAG, "Activity is not supported in current flavor");
77             finish();
78         }
79         if (savedInstanceState == null) {
80             final Fragment fragment = createSettingsFragment();
81             if (fragment == null) {
82                 return;
83             }
84             if (isStartupVerificationRequired()) {
85                 if (FlavorUtils.getFeatureFactory(this)
86                         .getStartupVerificationFeatureProvider()
87                         .startStartupVerificationActivityForResult(
88                                 this, REQUEST_CODE_STARTUP_VERIFICATION)) {
89                     return;
90                 }
91             }
92             if (FlavorUtils.isTwoPanel(this)) {
93                 getSupportFragmentManager().beginTransaction()
94                         .setCustomAnimations(android.R.animator.fade_in,
95                                 android.R.animator.fade_out)
96                         .add(android.R.id.content, fragment, SETTINGS_FRAGMENT_TAG)
97                         .commitNowAllowingStateLoss();
98                 return;
99             }
100 
101             final ViewGroup root = findViewById(android.R.id.content);
102             root.getViewTreeObserver().addOnPreDrawListener(
103                     new ViewTreeObserver.OnPreDrawListener() {
104                         @Override
105                         public boolean onPreDraw() {
106                             root.getViewTreeObserver().removeOnPreDrawListener(this);
107                             final Scene scene = new Scene(root);
108                             scene.setEnterAction(() -> {
109                                 if (getFragmentManager().isStateSaved()
110                                         || getFragmentManager().isDestroyed()) {
111                                     Log.d(TAG, "Got torn down before adding fragment");
112                                     return;
113                                 }
114                                 getSupportFragmentManager().beginTransaction()
115                                         .add(android.R.id.content, fragment,
116                                                 SETTINGS_FRAGMENT_TAG)
117                                         .commitNowAllowingStateLoss();
118                             });
119 
120                             final Slide slide = new Slide(Gravity.END);
121                             slide.setSlideFraction(
122                                     getResources().getDimension(R.dimen.lb_settings_pane_width)
123                                             / root.getWidth());
124                             TransitionManager.go(scene, slide);
125 
126                             // Skip the current draw, there's nothing in it
127                             return false;
128                         }
129                     });
130         }
131     }
132 
133     @Override
finish()134     public void finish() {
135         final Fragment fragment = getSupportFragmentManager().findFragmentByTag(
136                 SETTINGS_FRAGMENT_TAG);
137         if (FlavorUtils.isTwoPanel(this)) {
138             super.finish();
139             return;
140         }
141 
142         if (isResumed() && fragment != null) {
143             final ViewGroup root = findViewById(android.R.id.content);
144             final Scene scene = new Scene(root);
145             scene.setEnterAction(() -> getSupportFragmentManager().beginTransaction()
146                     .remove(fragment)
147                     .commitNowAllowingStateLoss());
148             final Slide slide = new Slide(Gravity.END);
149             slide.setSlideFraction(
150                     getResources().getDimension(R.dimen.lb_settings_pane_width) / root.getWidth());
151             slide.addListener(new Transition.TransitionListener() {
152                 @Override
153                 public void onTransitionStart(Transition transition) {
154                     getWindow().setDimAmount(0);
155                 }
156 
157                 @Override
158                 public void onTransitionEnd(Transition transition) {
159                     transition.removeListener(this);
160                     TvSettingsActivity.super.finish();
161                 }
162 
163                 @Override
164                 public void onTransitionCancel(Transition transition) {
165                 }
166 
167                 @Override
168                 public void onTransitionPause(Transition transition) {
169                 }
170 
171                 @Override
172                 public void onTransitionResume(Transition transition) {
173                 }
174             });
175             TransitionManager.go(scene, slide);
176         } else {
177             super.finish();
178         }
179     }
180 
createSettingsFragment()181     protected abstract Fragment createSettingsFragment();
182 
183     /**
184      * Subclass may override this to return true to indicate that the Activity may only be started
185      * after some verification. Example: in special mode, we need to challenge the user with re-auth
186      * before launching account settings.
187      *
188      * This only works in certain flavors as we do not have features requiring the startup
189      * verification in classic flavor or ordinary two panel flavor.
190      */
isStartupVerificationRequired()191     protected boolean isStartupVerificationRequired() {
192         return false;
193     }
194 
195     /** Subclass may override this to specify the flavor, in which the activity is available. */
getAvailableFlavors()196     protected int getAvailableFlavors() {
197         return ALL_FLAVORS_MASK;
198     }
199 
200     @Override
onActivityResult(int requestCode, int resultCode, Intent data)201     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
202         super.onActivityResult(requestCode, resultCode, data);
203         if (requestCode == REQUEST_CODE_STARTUP_VERIFICATION) {
204             if (resultCode == RESULT_OK) {
205                 Log.v(TAG, "Startup verification succeeded.");
206                 if (FlavorUtils.getFlavor(this) == FlavorUtils.FLAVOR_X
207                         || FlavorUtils.getFlavor(this) == FlavorUtils.FLAVOR_VENDOR) {
208                     if (createSettingsFragment() == null) {
209                         Log.e(TAG, "Fragment is null.");
210                         finish();
211                         return;
212                     }
213                     getSupportFragmentManager().beginTransaction()
214                             .setCustomAnimations(
215                                     android.R.animator.fade_in, android.R.animator.fade_out)
216                             .add(
217                                     android.R.id.content,
218                                     createSettingsFragment(),
219                                     SETTINGS_FRAGMENT_TAG)
220                             .commitNowAllowingStateLoss();
221                 }
222             } else {
223                 Log.v(TAG, "Startup verification cancelled or failed.");
224                 finish();
225             }
226         }
227     }
228 
229     @Override
onPause()230     public void onPause() {
231         super.onPause();
232         mHandler.removeCallbacksAndMessages(null);
233     }
234 }
235