1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.fragments;
16 
17 import android.annotation.Nullable;
18 import android.app.Fragment;
19 import android.app.FragmentController;
20 import android.app.FragmentHostCallback;
21 import android.app.FragmentManager;
22 import android.app.FragmentManager.FragmentLifecycleCallbacks;
23 import android.app.FragmentManagerNonConfig;
24 import android.content.Context;
25 import android.content.pm.ActivityInfo;
26 import android.content.res.Configuration;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.os.Parcelable;
31 import android.os.Trace;
32 import android.util.ArrayMap;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 
36 import androidx.annotation.NonNull;
37 
38 import com.android.settingslib.applications.InterestingConfigChanges;
39 import com.android.systemui.plugins.Plugin;
40 import com.android.systemui.util.leak.LeakDetector;
41 
42 import dagger.assisted.Assisted;
43 import dagger.assisted.AssistedFactory;
44 import dagger.assisted.AssistedInject;
45 
46 import java.io.FileDescriptor;
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.HashMap;
50 
51 import javax.inject.Provider;
52 
53 public class FragmentHostManager {
54 
55     private final Handler mHandler = new Handler(Looper.getMainLooper());
56     private final Context mContext;
57     private final HashMap<String, ArrayList<FragmentListener>> mListeners = new HashMap<>();
58     private final View mRootView;
59     private final LeakDetector mLeakDetector;
60     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
61             ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
62                     | ActivityInfo.CONFIG_ASSETS_PATHS);
63     private final FragmentService mManager;
64     private final ExtensionFragmentManager mPlugins = new ExtensionFragmentManager();
65 
66     private FragmentController mFragments;
67     private FragmentLifecycleCallbacks mLifecycleCallbacks;
68 
69     @AssistedInject
FragmentHostManager( @ssisted View rootView, FragmentService manager, LeakDetector leakDetector)70     FragmentHostManager(
71             @Assisted View rootView,
72             FragmentService manager,
73             LeakDetector leakDetector) {
74         mContext = rootView.getContext();
75         mManager = manager;
76         mRootView = rootView;
77         mLeakDetector = leakDetector;
78         mConfigChanges.applyNewConfig(mContext.getResources());
79         createFragmentHost(null);
80     }
81 
82     @AssistedFactory
83     public interface Factory {
create(View rootView)84         FragmentHostManager create(View rootView);
85     }
86 
createFragmentHost(Parcelable savedState)87     private void createFragmentHost(Parcelable savedState) {
88         mFragments = FragmentController.createController(new HostCallbacks());
89         mFragments.attachHost(null);
90         mLifecycleCallbacks = new FragmentLifecycleCallbacks() {
91             @Override
92             public void onFragmentViewCreated(FragmentManager fm, Fragment f, View v,
93                     Bundle savedInstanceState) {
94                 FragmentHostManager.this.onFragmentViewCreated(f);
95             }
96 
97             @Override
98             public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {
99                 FragmentHostManager.this.onFragmentViewDestroyed(f);
100             }
101 
102             @Override
103             public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
104                 mLeakDetector.trackGarbage(f);
105             }
106         };
107         mFragments.getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks,
108                 true);
109         if (savedState != null) {
110             mFragments.restoreAllState(savedState, (FragmentManagerNonConfig) null);
111         }
112         // For now just keep all fragments in the resumed state.
113         mFragments.dispatchCreate();
114         mFragments.dispatchStart();
115         mFragments.dispatchResume();
116     }
117 
destroyFragmentHost()118     private Parcelable destroyFragmentHost() {
119         mFragments.dispatchPause();
120         Parcelable p = mFragments.saveAllState();
121         mFragments.dispatchStop();
122         mFragments.dispatchDestroy();
123         mFragments.getFragmentManager().unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks);
124         return p;
125     }
126 
127     /**
128      * Add a {@link FragmentListener} for a given tag
129      *
130      * @param tag string identifier for the fragment
131      * @param listener the listener to register
132      *
133      * @return this
134      */
addTagListener( @onNull String tag, @NonNull FragmentListener listener )135     public FragmentHostManager addTagListener(
136             @NonNull String tag,
137             @NonNull FragmentListener listener
138     ) {
139         ArrayList<FragmentListener> listeners = mListeners.get(tag);
140         if (listeners == null) {
141             listeners = new ArrayList<>();
142             mListeners.put(tag, listeners);
143         }
144         listeners.add(listener);
145         Fragment current = getFragmentManager().findFragmentByTag(tag);
146         if (current != null && current.getView() != null) {
147             listener.onFragmentViewCreated(tag, current);
148         }
149         return this;
150     }
151 
152     // Shouldn't generally be needed, included for completeness sake.
removeTagListener(String tag, FragmentListener listener)153     public void removeTagListener(String tag, FragmentListener listener) {
154         ArrayList<FragmentListener> listeners = mListeners.get(tag);
155         if (listeners != null && listeners.remove(listener) && listeners.size() == 0) {
156             mListeners.remove(tag);
157         }
158     }
159 
onFragmentViewCreated(Fragment fragment)160     private void onFragmentViewCreated(Fragment fragment) {
161         String tag = fragment.getTag();
162 
163         ArrayList<FragmentListener> listeners = mListeners.get(tag);
164         if (listeners != null) {
165             listeners.forEach((listener) -> listener.onFragmentViewCreated(tag, fragment));
166         }
167     }
168 
onFragmentViewDestroyed(Fragment fragment)169     private void onFragmentViewDestroyed(Fragment fragment) {
170         String tag = fragment.getTag();
171 
172         ArrayList<FragmentListener> listeners = mListeners.get(tag);
173         if (listeners != null) {
174             listeners.forEach((listener) -> listener.onFragmentViewDestroyed(tag, fragment));
175         }
176     }
177 
178     /**
179      * Called when the configuration changed, return true if the fragments
180      * should be recreated.
181      */
onConfigurationChanged(Configuration newConfig)182     protected void onConfigurationChanged(Configuration newConfig) {
183         if (mConfigChanges.applyNewConfig(mContext.getResources())) {
184             reloadFragments();
185         } else {
186             mFragments.dispatchConfigurationChanged(newConfig);
187         }
188     }
189 
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)190     private void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
191         // TODO: Do something?
192     }
193 
findViewById(int id)194     private <T extends View> T findViewById(int id) {
195         return mRootView.findViewById(id);
196     }
197 
198     /**
199      * Note: Values from this shouldn't be cached as they can change after config changes.
200      */
getFragmentManager()201     public FragmentManager getFragmentManager() {
202         return mFragments.getFragmentManager();
203     }
204 
getExtensionManager()205     ExtensionFragmentManager getExtensionManager() {
206         return mPlugins;
207     }
208 
destroy()209     void destroy() {
210         mFragments.dispatchDestroy();
211     }
212 
213     /**
214      * Creates a fragment that requires injection.
215      */
create(Class<T> fragmentCls)216     public <T> T create(Class<T> fragmentCls) {
217         return (T) mPlugins.instantiate(mContext, fragmentCls.getName(), null);
218     }
219 
220     public interface FragmentListener {
onFragmentViewCreated(String tag, Fragment fragment)221         void onFragmentViewCreated(String tag, Fragment fragment);
222 
223         // The facts of lifecycle
224         // When a fragment is destroyed, you should not talk to it any longer.
onFragmentViewDestroyed(String tag, Fragment fragment)225         default void onFragmentViewDestroyed(String tag, Fragment fragment) {
226         }
227     }
228 
reloadFragments()229     public void reloadFragments() {
230         Trace.beginSection("FrargmentHostManager#reloadFragments");
231         // Save the old state.
232         Parcelable p = destroyFragmentHost();
233         // Generate a new fragment host and restore its state.
234         createFragmentHost(p);
235         Trace.endSection();
236     }
237 
238     class HostCallbacks extends FragmentHostCallback<FragmentHostManager> {
HostCallbacks()239         public HostCallbacks() {
240             super(mContext, FragmentHostManager.this.mHandler, 0);
241         }
242 
243         @Override
onGetHost()244         public FragmentHostManager onGetHost() {
245             return FragmentHostManager.this;
246         }
247 
248         @Override
onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)249         public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
250             FragmentHostManager.this.dump(prefix, fd, writer, args);
251         }
252 
253         @Override
instantiate(Context context, String className, Bundle arguments)254         public Fragment instantiate(Context context, String className, Bundle arguments) {
255             return mPlugins.instantiate(context, className, arguments);
256         }
257 
258         @Override
onShouldSaveFragmentState(Fragment fragment)259         public boolean onShouldSaveFragmentState(Fragment fragment) {
260             return true; // True for now.
261         }
262 
263         @Override
onGetLayoutInflater()264         public LayoutInflater onGetLayoutInflater() {
265             return LayoutInflater.from(mContext);
266         }
267 
268         @Override
onUseFragmentManagerInflaterFactory()269         public boolean onUseFragmentManagerInflaterFactory() {
270             return true;
271         }
272 
273         @Override
onHasWindowAnimations()274         public boolean onHasWindowAnimations() {
275             return false;
276         }
277 
278         @Override
onGetWindowAnimations()279         public int onGetWindowAnimations() {
280             return 0;
281         }
282 
283         @Override
onAttachFragment(Fragment fragment)284         public void onAttachFragment(Fragment fragment) {
285         }
286 
287         @Override
288         @Nullable
onFindViewById(int id)289         public <T extends View> T onFindViewById(int id) {
290             return FragmentHostManager.this.findViewById(id);
291         }
292 
293         @Override
onHasView()294         public boolean onHasView() {
295             return true;
296         }
297     }
298 
299     class ExtensionFragmentManager {
300         private final ArrayMap<String, Context> mExtensionLookup = new ArrayMap<>();
301 
setCurrentExtension(int id, @NonNull String tag, @Nullable String oldClass, @NonNull String currentClass, @Nullable Context context)302         public void setCurrentExtension(int id, @NonNull  String tag, @Nullable String oldClass,
303                 @NonNull String currentClass, @Nullable Context context) {
304             if (oldClass != null) {
305                 mExtensionLookup.remove(oldClass);
306             }
307             mExtensionLookup.put(currentClass, context);
308             getFragmentManager().beginTransaction()
309                     .replace(id, instantiate(context, currentClass, null), tag)
310                     .commit();
311             reloadFragments();
312         }
313 
instantiate(Context context, String className, Bundle arguments)314         Fragment instantiate(Context context, String className, Bundle arguments) {
315             Context extensionContext = mExtensionLookup.get(className);
316             if (extensionContext != null) {
317                 Fragment f = instantiateWithInjections(extensionContext, className, arguments);
318                 if (f instanceof Plugin) {
319                     ((Plugin) f).onCreate(mContext, extensionContext);
320                 }
321                 return f;
322             }
323             return instantiateWithInjections(context, className, arguments);
324         }
325 
instantiateWithInjections(Context context, String className, Bundle args)326         private Fragment instantiateWithInjections(Context context, String className, Bundle args) {
327             Provider<? extends Fragment> fragmentProvider =
328                     mManager.getInjectionMap().get(className);
329             if (fragmentProvider != null) {
330                 Fragment f = fragmentProvider.get();
331                 // Setup the args, taken from Fragment#instantiate.
332                 if (args != null) {
333                     args.setClassLoader(f.getClass().getClassLoader());
334                     f.setArguments(args);
335                 }
336                 return f;
337             }
338             return Fragment.instantiate(context, className, args);
339         }
340     }
341 
342     private static class PluginState {
343         Context mContext;
344         String mCls;
345 
PluginState(String cls, Context context)346         private PluginState(String cls, Context context) {
347             mCls = cls;
348             mContext = context;
349         }
350     }
351 }
352