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