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