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