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