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