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