Fragments in SysUI!
Add system to add fragments to sysui windows for better code modularity and testability. Bug: 32609190 Test: Manual Change-Id: I2ae7ed6133aff3fc5cdbdb3ec89d55183b7ac797
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user