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