diff options
3 files changed, 324 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 99e787676ef5..359c33019600 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -29,6 +29,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.util.Log; +import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyboard.KeyboardUI; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.media.RingtonePlayer; @@ -61,6 +62,7 @@ public class SystemUIApplication extends Application { * The classes of the stuff to start. */ private final Class<?>[] SERVICES = new Class[] { + FragmentService.class, TunerService.class, KeyguardViewMediator.class, Recents.class, diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java new file mode 100644 index 000000000000..634e5974a39e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.fragments; + +import android.annotation.Nullable; +import android.app.Fragment; +import android.app.FragmentController; +import android.app.FragmentHostCallback; +import android.app.FragmentManager; +import android.app.FragmentManager.FragmentLifecycleCallbacks; +import android.app.FragmentManagerNonConfig; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Parcelable; +import android.view.LayoutInflater; +import android.view.View; + +import com.android.settingslib.applications.InterestingConfigChanges; +import com.android.systemui.SystemUIApplication; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; + +public class FragmentHostManager { + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Context mContext; + private final HashMap<String, ArrayList<FragmentListener>> mListeners = new HashMap<>(); + private final View mRootView; + private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(); + private final FragmentService mManager; + + private FragmentController mFragments; + private FragmentLifecycleCallbacks mLifecycleCallbacks; + + FragmentHostManager(Context context, FragmentService manager, View rootView) { + mContext = context; + mManager = manager; + mRootView = rootView; + mConfigChanges.applyNewConfig(context.getResources()); + createFragmentHost(null); + } + + private void createFragmentHost(Parcelable savedState) { + mFragments = FragmentController.createController(new HostCallbacks()); + mFragments.attachHost(null); + // TODO: Remove non-staticness from FragmentLifecycleCallbacks (hopefully). + mLifecycleCallbacks = mFragments.getFragmentManager().new FragmentLifecycleCallbacks() { + @Override + public void onFragmentViewCreated(FragmentManager fm, Fragment f, View v, + Bundle savedInstanceState) { + FragmentHostManager.this.onFragmentViewCreated(f); + } + + @Override + public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) { + FragmentHostManager.this.onFragmentViewDestroyed(f); + } + }; + mFragments.getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks, + true); + if (savedState != null) { + mFragments.restoreAllState(savedState, (FragmentManagerNonConfig) null); + } + // For now just keep all fragments in the resumed state. + mFragments.dispatchCreate(); + mFragments.dispatchStart(); + mFragments.dispatchResume(); + } + + private Parcelable destroyFragmentHost() { + mFragments.dispatchPause(); + Parcelable p = mFragments.saveAllState(); + mFragments.dispatchStop(); + mFragments.dispatchDestroy(); + mFragments.getFragmentManager().unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks); + return p; + } + + public void addTagListener(String tag, FragmentListener listener) { + ArrayList<FragmentListener> listeners = mListeners.get(tag); + if (listeners == null) { + listeners = new ArrayList<>(); + mListeners.put(tag, listeners); + } + listeners.add(listener); + Fragment current = getFragmentManager().findFragmentByTag(tag); + if (current != null && current.getView() != null) { + listener.onFragmentViewCreated(tag, current); + } + } + + // Shouldn't generally be needed, included for completeness sake. + public void removeTagListener(String tag, FragmentListener listener) { + ArrayList<FragmentListener> listeners = mListeners.get(tag); + if (listeners != null && listeners.remove(listener) && listeners.size() == 0) { + mListeners.remove(tag); + } + } + + private void onFragmentViewCreated(Fragment fragment) { + String tag = fragment.getTag(); + + ArrayList<FragmentListener> listeners = mListeners.get(tag); + if (listeners != null) { + listeners.forEach((listener) -> listener.onFragmentViewCreated(tag, fragment)); + } + } + + private void onFragmentViewDestroyed(Fragment fragment) { + String tag = fragment.getTag(); + + ArrayList<FragmentListener> listeners = mListeners.get(tag); + if (listeners != null) { + listeners.forEach((listener) -> listener.onFragmentViewDestroyed(tag, fragment)); + } + } + + /** + * Called when the configuration changed, return true if the fragments + * should be recreated. + */ + protected void onConfigurationChanged(Configuration newConfig) { + if (mConfigChanges.applyNewConfig(mContext.getResources())) { + // Save the old state. + Parcelable p = destroyFragmentHost(); + // Generate a new fragment host and restore its state. + createFragmentHost(p); + } else { + mFragments.dispatchConfigurationChanged(newConfig); + } + } + + private void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + // TODO: Do something? + } + + private View findViewById(int id) { + return mRootView.findViewById(id); + } + + /** + * Note: Values from this shouldn't be cached as they can change after config changes. + */ + public FragmentManager getFragmentManager() { + return mFragments.getFragmentManager(); + } + + public interface FragmentListener { + void onFragmentViewCreated(String tag, Fragment fragment); + + // The facts of lifecycle + // When a fragment is destroyed, you should not talk to it any longer. + default void onFragmentViewDestroyed(String tag, Fragment fragment) { + } + } + + public static FragmentHostManager get(View view) { + try { + return ((SystemUIApplication) view.getContext().getApplicationContext()) + .getComponent(FragmentService.class).getFragmentHostManager(view); + } catch (ClassCastException e) { + // TODO: Some auto handling here? + throw e; + } + } + + class HostCallbacks extends FragmentHostCallback<FragmentHostManager> { + public HostCallbacks() { + super(mContext, FragmentHostManager.this.mHandler, 0); + } + + @Override + public FragmentHostManager onGetHost() { + return FragmentHostManager.this; + } + + @Override + public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + FragmentHostManager.this.dump(prefix, fd, writer, args); + } + + @Override + public boolean onShouldSaveFragmentState(Fragment fragment) { + return true; // True for now. + } + + @Override + public LayoutInflater onGetLayoutInflater() { + return LayoutInflater.from(mContext); + } + + @Override + public boolean onUseFragmentManagerInflaterFactory() { + return true; + } + + @Override + public boolean onHasWindowAnimations() { + return false; + } + + @Override + public int onGetWindowAnimations() { + return 0; + } + + @Override + public void onAttachFragment(Fragment fragment) { + } + + @Override + @Nullable + public View onFindViewById(int id) { + return FragmentHostManager.this.findViewById(id); + } + + @Override + public boolean onHasView() { + return true; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java new file mode 100644 index 000000000000..85cde10b5a53 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.fragments; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.Handler; +import android.util.ArrayMap; +import android.util.Log; +import android.view.View; + +import com.android.systemui.SystemUI; +import com.android.systemui.SystemUIApplication; + +/** + * Holds a map of root views to FragmentHostStates and generates them as needed. + * Also dispatches the configuration changes to all current FragmentHostStates. + */ +public class FragmentService extends SystemUI { + + private static final String TAG = "FragmentService"; + + private final ArrayMap<View, FragmentHostState> mHosts = new ArrayMap<>(); + private final Handler mHandler = new Handler(); + + @Override + public void start() { + putComponent(FragmentService.class, this); + } + + public FragmentHostManager getFragmentHostManager(View view) { + View root = view.getRootView(); + FragmentHostState state = mHosts.get(root); + if (state == null) { + state = new FragmentHostState(root); + mHosts.put(root, state); + } + return state.getFragmentHostManager(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + for (FragmentHostState state : mHosts.values()) { + state.sendConfigurationChange(newConfig); + } + } + + private class FragmentHostState { + private final View mView; + + private FragmentHostManager mFragmentHostManager; + + public FragmentHostState(View view) { + mView = view; + mFragmentHostManager = new FragmentHostManager(mContext, FragmentService.this, mView); + } + + public void sendConfigurationChange(Configuration newConfig) { + mHandler.post(() -> handleSendConfigurationChange(newConfig)); + } + + public FragmentHostManager getFragmentHostManager() { + return mFragmentHostManager; + } + + private void handleSendConfigurationChange(Configuration newConfig) { + mFragmentHostManager.onConfigurationChanged(newConfig); + } + } +} |