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 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 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 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 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. 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 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 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 */ 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 163 private void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 164 // TODO: Do something? 165 } 166 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 */ 174 public FragmentManager getFragmentManager() { 175 return mFragments.getFragmentManager(); 176 } 177 178 PluginFragmentManager getPluginManager() { 179 return mPlugins; 180 } 181 182 void destroy() { 183 mFragments.dispatchDestroy(); 184 } 185 186 public interface FragmentListener { 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. 191 default void onFragmentViewDestroyed(String tag, Fragment fragment) { 192 } 193 } 194 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> { 205 public HostCallbacks() { 206 super(mContext, FragmentHostManager.this.mHandler, 0); 207 } 208 209 @Override 210 public FragmentHostManager onGetHost() { 211 return FragmentHostManager.this; 212 } 213 214 @Override 215 public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 216 FragmentHostManager.this.dump(prefix, fd, writer, args); 217 } 218 219 @Override 220 public Fragment instantiate(Context context, String className, Bundle arguments) { 221 return mPlugins.instantiate(context, className, arguments); 222 } 223 224 @Override 225 public boolean onShouldSaveFragmentState(Fragment fragment) { 226 return true; // True for now. 227 } 228 229 @Override 230 public LayoutInflater onGetLayoutInflater() { 231 return LayoutInflater.from(mContext); 232 } 233 234 @Override 235 public boolean onUseFragmentManagerInflaterFactory() { 236 return true; 237 } 238 239 @Override 240 public boolean onHasWindowAnimations() { 241 return false; 242 } 243 244 @Override 245 public int onGetWindowAnimations() { 246 return 0; 247 } 248 249 @Override 250 public void onAttachFragment(Fragment fragment) { 251 } 252 253 @Override 254 @Nullable 255 public <T extends View> T onFindViewById(int id) { 256 return FragmentHostManager.this.findViewById(id); 257 } 258 259 @Override 260 public boolean onHasView() { 261 return true; 262 } 263 } 264 265 class PluginFragmentManager { 266 private final ArrayMap<String, Context> mPluginLookup = new ArrayMap<>(); 267 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 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 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 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 312 private PluginState(String cls, Context context) { 313 mCls = cls; 314 mContext = context; 315 } 316 } 317 } 318