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