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