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