blob: f8f364da089b9a6bfa5e36cb344bff4392f0b81e [file] [log] [blame]
Jason Monkbbac1212016-10-31 10:18:20 -04001/*
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
15package com.android.systemui.fragments;
16
17import android.annotation.Nullable;
18import android.app.Fragment;
19import android.app.FragmentController;
20import android.app.FragmentHostCallback;
21import android.app.FragmentManager;
22import android.app.FragmentManager.FragmentLifecycleCallbacks;
23import android.app.FragmentManagerNonConfig;
24import android.content.Context;
Jason Monke33d3332017-03-23 11:07:00 -040025import android.content.pm.ActivityInfo;
Jason Monkbbac1212016-10-31 10:18:20 -040026import android.content.res.Configuration;
27import android.os.Bundle;
28import android.os.Handler;
29import android.os.Looper;
30import android.os.Parcelable;
Jason Monk0b80c4e2017-05-01 15:38:34 -040031import android.support.annotation.NonNull;
Jason Monk20ff3f92017-01-09 15:13:23 -050032import android.util.ArrayMap;
Jason Monkbbac1212016-10-31 10:18:20 -040033import android.view.LayoutInflater;
34import android.view.View;
35
36import com.android.settingslib.applications.InterestingConfigChanges;
Jason Monk790442e2017-02-13 17:49:39 -050037import com.android.systemui.Dependency;
Jason Monk20ff3f92017-01-09 15:13:23 -050038import com.android.systemui.plugins.Plugin;
Adrian Roos40232172017-04-13 09:14:51 -070039import com.android.systemui.util.leak.LeakDetector;
Jason Monkbbac1212016-10-31 10:18:20 -040040
41import java.io.FileDescriptor;
42import java.io.PrintWriter;
43import java.util.ArrayList;
44import java.util.HashMap;
45
46public 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;
Jason Monke33d3332017-03-23 11:07:00 -040052 private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
Jason Monk3edad312017-06-05 11:33:48 -040053 ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
54 | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
Jason Monkbbac1212016-10-31 10:18:20 -040055 private final FragmentService mManager;
Jason Monk0b80c4e2017-05-01 15:38:34 -040056 private final ExtensionFragmentManager mPlugins = new ExtensionFragmentManager();
Jason Monkbbac1212016-10-31 10:18:20 -040057
58 private FragmentController mFragments;
59 private FragmentLifecycleCallbacks mLifecycleCallbacks;
60
61 FragmentHostManager(Context context, FragmentService manager, View rootView) {
Jason Monk49fa0162017-01-11 09:21:56 -050062 mContext = context;
Jason Monkbbac1212016-10-31 10:18:20 -040063 mManager = manager;
64 mRootView = rootView;
65 mConfigChanges.applyNewConfig(context.getResources());
66 createFragmentHost(null);
67 }
68
69 private void createFragmentHost(Parcelable savedState) {
70 mFragments = FragmentController.createController(new HostCallbacks());
71 mFragments.attachHost(null);
Adam Powell74827260a2016-11-16 16:20:45 -080072 mLifecycleCallbacks = new FragmentLifecycleCallbacks() {
Jason Monkbbac1212016-10-31 10:18:20 -040073 @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 }
Adrian Roos40232172017-04-13 09:14:51 -070083
84 @Override
85 public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
86 Dependency.get(LeakDetector.class).trackGarbage(f);
87 }
Jason Monkbbac1212016-10-31 10:18:20 -040088 };
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
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
Jason Monkaa573e92017-01-27 17:00:29 -0500109 public FragmentHostManager addTagListener(String tag, FragmentListener listener) {
Jason Monkbbac1212016-10-31 10:18:20 -0400110 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 }
Jason Monkaa573e92017-01-27 17:00:29 -0500120 return this;
Jason Monkbbac1212016-10-31 10:18:20 -0400121 }
122
123 // Shouldn't generally be needed, included for completeness sake.
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
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
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 */
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
164 private void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
165 // TODO: Do something?
166 }
167
Alan Viverette04fd4702017-04-13 16:37:06 -0400168 private <T extends View> T findViewById(int id) {
Jason Monkbbac1212016-10-31 10:18:20 -0400169 return mRootView.findViewById(id);
170 }
171
172 /**
173 * Note: Values from this shouldn't be cached as they can change after config changes.
174 */
175 public FragmentManager getFragmentManager() {
176 return mFragments.getFragmentManager();
177 }
178
Jason Monk0b80c4e2017-05-01 15:38:34 -0400179 ExtensionFragmentManager getExtensionManager() {
Jason Monk20ff3f92017-01-09 15:13:23 -0500180 return mPlugins;
181 }
182
Jason Monk790442e2017-02-13 17:49:39 -0500183 void destroy() {
184 mFragments.dispatchDestroy();
185 }
186
Jason Monkbbac1212016-10-31 10:18:20 -0400187 public interface FragmentListener {
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.
192 default void onFragmentViewDestroyed(String tag, Fragment fragment) {
193 }
194 }
195
196 public static FragmentHostManager get(View view) {
197 try {
Jason Monk790442e2017-02-13 17:49:39 -0500198 return Dependency.get(FragmentService.class).getFragmentHostManager(view);
Jason Monkbbac1212016-10-31 10:18:20 -0400199 } catch (ClassCastException e) {
200 // TODO: Some auto handling here?
201 throw e;
202 }
203 }
204
205 class HostCallbacks extends FragmentHostCallback<FragmentHostManager> {
206 public HostCallbacks() {
207 super(mContext, FragmentHostManager.this.mHandler, 0);
208 }
209
210 @Override
211 public FragmentHostManager onGetHost() {
212 return FragmentHostManager.this;
213 }
214
215 @Override
216 public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
217 FragmentHostManager.this.dump(prefix, fd, writer, args);
218 }
219
220 @Override
Jason Monk20ff3f92017-01-09 15:13:23 -0500221 public Fragment instantiate(Context context, String className, Bundle arguments) {
222 return mPlugins.instantiate(context, className, arguments);
223 }
224
225 @Override
Jason Monkbbac1212016-10-31 10:18:20 -0400226 public boolean onShouldSaveFragmentState(Fragment fragment) {
227 return true; // True for now.
228 }
229
230 @Override
231 public LayoutInflater onGetLayoutInflater() {
232 return LayoutInflater.from(mContext);
233 }
234
235 @Override
236 public boolean onUseFragmentManagerInflaterFactory() {
237 return true;
238 }
239
240 @Override
241 public boolean onHasWindowAnimations() {
242 return false;
243 }
244
245 @Override
246 public int onGetWindowAnimations() {
247 return 0;
248 }
249
250 @Override
251 public void onAttachFragment(Fragment fragment) {
252 }
253
254 @Override
255 @Nullable
Alan Viverette04fd4702017-04-13 16:37:06 -0400256 public <T extends View> T onFindViewById(int id) {
Jason Monkbbac1212016-10-31 10:18:20 -0400257 return FragmentHostManager.this.findViewById(id);
258 }
259
260 @Override
261 public boolean onHasView() {
262 return true;
263 }
264 }
Jason Monk20ff3f92017-01-09 15:13:23 -0500265
Jason Monk0b80c4e2017-05-01 15:38:34 -0400266 class ExtensionFragmentManager {
267 private final ArrayMap<String, Context> mExtensionLookup = new ArrayMap<>();
Jason Monk20ff3f92017-01-09 15:13:23 -0500268
Jason Monk153acbc2017-06-22 10:09:29 -0400269 public void setCurrentExtension(int id, @NonNull String tag, @Nullable String oldClass,
Jason Monk0b80c4e2017-05-01 15:38:34 -0400270 @NonNull String currentClass, @Nullable Context context) {
Jason Monk0b80c4e2017-05-01 15:38:34 -0400271 if (oldClass != null) {
272 mExtensionLookup.remove(oldClass);
273 }
274 mExtensionLookup.put(currentClass, context);
Jason Monk20ff3f92017-01-09 15:13:23 -0500275 getFragmentManager().beginTransaction()
Jason Monk153acbc2017-06-22 10:09:29 -0400276 .replace(id, instantiate(context, currentClass, null), tag)
Jason Monk20ff3f92017-01-09 15:13:23 -0500277 .commit();
278 reloadFragments();
279 }
280
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
288 Fragment instantiate(Context context, String className, Bundle arguments) {
Jason Monk0b80c4e2017-05-01 15:38:34 -0400289 Context extensionContext = mExtensionLookup.get(className);
290 if (extensionContext != null) {
291 Fragment f = Fragment.instantiate(extensionContext, className, arguments);
Jason Monk20ff3f92017-01-09 15:13:23 -0500292 if (f instanceof Plugin) {
Jason Monk0b80c4e2017-05-01 15:38:34 -0400293 ((Plugin) f).onCreate(mContext, extensionContext);
Jason Monk20ff3f92017-01-09 15:13:23 -0500294 }
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
305 private PluginState(String cls, Context context) {
306 mCls = cls;
307 mContext = context;
308 }
309 }
Jason Monkbbac1212016-10-31 10:18:20 -0400310}