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