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