diff options
Diffstat (limited to 'mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences')
2 files changed, 552 insertions, 0 deletions
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences/PreferenceFragment.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences/PreferenceFragment.java new file mode 100644 index 000000000..5bc5422c8 --- /dev/null +++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences/PreferenceFragment.java @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2013 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 org.mozilla.gecko.background.preferences; + +import org.mozilla.gecko.R; +import org.mozilla.gecko.util.WeakReferenceHandler; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.preference.Preference; +import android.preference.PreferenceGroup; +import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; +import android.support.v4.app.Fragment; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnKeyListener; +import android.view.ViewGroup; +import android.widget.ListView; + +public abstract class PreferenceFragment extends Fragment implements PreferenceManagerCompat.OnPreferenceTreeClickListener { + private static final String PREFERENCES_TAG = "android:preferences"; + + private PreferenceManager mPreferenceManager; + private ListView mList; + private boolean mHavePrefs; + private boolean mInitDone; + + /** + * The starting request code given out to preference framework. + */ + private static final int FIRST_REQUEST_CODE = 100; + + private static final int MSG_BIND_PREFERENCES = 1; + + private static class PreferenceFragmentHandler extends WeakReferenceHandler<PreferenceFragment> { + public PreferenceFragmentHandler(final PreferenceFragment that) { + super(that); + } + + @Override + public void handleMessage(Message msg) { + final PreferenceFragment that = mTarget.get(); + if (that == null) { + return; + } + + switch (msg.what) { + + case MSG_BIND_PREFERENCES: + that.bindPreferences(); + break; + } + } + } + + private final Handler mHandler = new PreferenceFragmentHandler(this); + + final private Runnable mRequestFocus = new Runnable() { + @Override + public void run() { + mList.focusableViewAvailable(mList); + } + }; + + /** + * Interface that PreferenceFragment's containing activity should + * implement to be able to process preference items that wish to + * switch to a new fragment. + */ + public interface OnPreferenceStartFragmentCallback { + /** + * Called when the user has clicked on a Preference that has + * a fragment class name associated with it. The implementation + * to should instantiate and switch to an instance of the given + * fragment. + */ + boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref); + } + + @Override + public void onCreate(Bundle paramBundle) { + super.onCreate(paramBundle); + mPreferenceManager = PreferenceManagerCompat.newInstance(getActivity(), FIRST_REQUEST_CODE); + PreferenceManagerCompat.setFragment(mPreferenceManager, this); + } + + @Override + public View onCreateView(LayoutInflater paramLayoutInflater, ViewGroup paramViewGroup, Bundle paramBundle) { + return paramLayoutInflater.inflate(R.layout.fxaccount_preference_list_fragment, paramViewGroup, + false); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + if (mHavePrefs) { + bindPreferences(); + } + + mInitDone = true; + + if (savedInstanceState != null) { + Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG); + if (container != null) { + final PreferenceScreen preferenceScreen = getPreferenceScreen(); + if (preferenceScreen != null) { + preferenceScreen.restoreHierarchyState(container); + } + } + } + } + + @Override + public void onStart() { + super.onStart(); + PreferenceManagerCompat.setOnPreferenceTreeClickListener(mPreferenceManager, this); + } + + @Override + public void onStop() { + super.onStop(); + PreferenceManagerCompat.dispatchActivityStop(mPreferenceManager); + PreferenceManagerCompat.setOnPreferenceTreeClickListener(mPreferenceManager, null); + } + + @Override + public void onDestroyView() { + mList = null; + mHandler.removeCallbacks(mRequestFocus); + mHandler.removeMessages(MSG_BIND_PREFERENCES); + super.onDestroyView(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + PreferenceManagerCompat.dispatchActivityDestroy(mPreferenceManager); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + final PreferenceScreen preferenceScreen = getPreferenceScreen(); + if (preferenceScreen != null) { + Bundle container = new Bundle(); + preferenceScreen.saveHierarchyState(container); + outState.putBundle(PREFERENCES_TAG, container); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + PreferenceManagerCompat.dispatchActivityResult(mPreferenceManager, requestCode, resultCode, data); + } + + /** + * Returns the {@link PreferenceManager} used by this fragment. + * @return The {@link PreferenceManager}. + */ + public PreferenceManager getPreferenceManager() { + return mPreferenceManager; + } + + /** + * Sets the root of the preference hierarchy that this fragment is showing. + * + * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. + */ + public void setPreferenceScreen(PreferenceScreen preferenceScreen) { + if (PreferenceManagerCompat.setPreferences(mPreferenceManager, preferenceScreen) && preferenceScreen != null) { + mHavePrefs = true; + if (mInitDone) { + postBindPreferences(); + } + } + } + + /** + * Gets the root of the preference hierarchy that this fragment is showing. + * + * @return The {@link PreferenceScreen} that is the root of the preference + * hierarchy. + */ + public PreferenceScreen getPreferenceScreen() { + return PreferenceManagerCompat.getPreferenceScreen(mPreferenceManager); + } + + /** + * Adds preferences from activities that match the given {@link Intent}. + * + * @param intent The {@link Intent} to query activities. + */ + public void addPreferencesFromIntent(Intent intent) { + requirePreferenceManager(); + + setPreferenceScreen(PreferenceManagerCompat.inflateFromIntent(mPreferenceManager, intent, getPreferenceScreen())); + } + + /** + * Inflates the given XML resource and adds the preference hierarchy to the current + * preference hierarchy. + * + * @param preferencesResId The XML resource ID to inflate. + */ + public void addPreferencesFromResource(int preferencesResId) { + requirePreferenceManager(); + + setPreferenceScreen(PreferenceManagerCompat.inflateFromResource(mPreferenceManager, getActivity(), + preferencesResId, getPreferenceScreen())); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, + Preference preference) { + //if (preference.getFragment() != null && + if ( + getActivity() instanceof OnPreferenceStartFragmentCallback) { + return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment( + this, preference); + } + return false; + } + + /** + * Finds a {@link Preference} based on its key. + * + * @param key The key of the preference to retrieve. + * @return The {@link Preference} with the key, or null. + * @see PreferenceGroup#findPreference(CharSequence) + */ + public Preference findPreference(CharSequence key) { + if (mPreferenceManager == null) { + return null; + } + return mPreferenceManager.findPreference(key); + } + + private void requirePreferenceManager() { + if (mPreferenceManager == null) { + throw new RuntimeException("This should be called after super.onCreate."); + } + } + + private void postBindPreferences() { + if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return; + mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); + } + + private void bindPreferences() { + final PreferenceScreen preferenceScreen = getPreferenceScreen(); + if (preferenceScreen != null) { + preferenceScreen.bind(getListView()); + } + } + + public ListView getListView() { + ensureList(); + return mList; + } + + private void ensureList() { + if (mList != null) { + return; + } + View root = getView(); + if (root == null) { + throw new IllegalStateException("Content view not yet created"); + } + View rawListView = root.findViewById(android.R.id.list); + if (!(rawListView instanceof ListView)) { + throw new RuntimeException( + "Content has view with id attribute 'android.R.id.list' " + + "that is not a ListView class"); + } + mList = (ListView)rawListView; + if (mList == null) { + throw new RuntimeException( + "Your content must have a ListView whose id attribute is " + + "'android.R.id.list'"); + } + mList.setOnKeyListener(mListOnKeyListener); + mHandler.post(mRequestFocus); + } + + private final OnKeyListener mListOnKeyListener = new OnKeyListener() { + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + Object selectedItem = mList.getSelectedItem(); + if (selectedItem instanceof Preference) { + @SuppressWarnings("unused") + View selectedView = mList.getSelectedView(); + //return ((Preference)selectedItem).onKey( + // selectedView, keyCode, event); + return false; + } + return false; + } + + }; +} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences/PreferenceManagerCompat.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences/PreferenceManagerCompat.java new file mode 100644 index 000000000..22c62e431 --- /dev/null +++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences/PreferenceManagerCompat.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2013 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 org.mozilla.gecko.background.preferences; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.preference.Preference; +import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; +import android.util.Log; + +public class PreferenceManagerCompat { + + private static final String TAG = PreferenceManagerCompat.class.getSimpleName(); + + /** + * Interface definition for a callback to be invoked when a {@link Preference} in the hierarchy + * rooted at this {@link PreferenceScreen} is clicked. + */ + interface OnPreferenceTreeClickListener { + /** + * Called when a preference in the tree rooted at this {@link PreferenceScreen} has been + * clicked. + * + * @param preferenceScreen The {@link PreferenceScreen} that the preference is located in. + * @param preference The preference that was clicked. + * + * @return Whether the click was handled. + */ + boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference); + } + + static PreferenceManager newInstance(Activity activity, int firstRequestCode) { + try { + Constructor<PreferenceManager> c = PreferenceManager.class.getDeclaredConstructor(Activity.class, int.class); + c.setAccessible(true); + return c.newInstance(activity, firstRequestCode); + } catch (Exception e) { + Log.w(TAG, "Couldn't call constructor PreferenceManager by reflection", e); + } + return null; + } + + /** + * Sets the owning preference fragment + */ + static void setFragment(PreferenceManager manager, PreferenceFragment fragment) { + // stub + } + + /** + * Sets the callback to be invoked when a {@link Preference} in the hierarchy rooted at this + * {@link PreferenceManager} is clicked. + * + * @param listener The callback to be invoked. + */ + static void setOnPreferenceTreeClickListener(PreferenceManager manager, final OnPreferenceTreeClickListener listener) { + try { + Field onPreferenceTreeClickListener = PreferenceManager.class.getDeclaredField("mOnPreferenceTreeClickListener"); + onPreferenceTreeClickListener.setAccessible(true); + if (listener != null) { + Object proxy = Proxy.newProxyInstance( + onPreferenceTreeClickListener.getType().getClassLoader(), + new Class<?>[] { onPreferenceTreeClickListener.getType() }, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + if (method.getName().equals("onPreferenceTreeClick")) { + return listener.onPreferenceTreeClick((PreferenceScreen) args[0], (Preference) args[1]); + } else { + return null; + } + } + }); + onPreferenceTreeClickListener.set(manager, proxy); + } else { + onPreferenceTreeClickListener.set(manager, null); + } + } catch (Exception e) { + Log.w(TAG, "Couldn't set PreferenceManager.mOnPreferenceTreeClickListener by reflection", e); + } + } + + /** + * Inflates a preference hierarchy from the preference hierarchies of {@link Activity Activities} + * that match the given {@link Intent}. An {@link Activity} defines its preference hierarchy with + * meta-data using the {@link #METADATA_KEY_PREFERENCES} key. + * <p/> + * If a preference hierarchy is given, the new preference hierarchies will be merged in. + * + * @param queryIntent The intent to match activities. + * @param rootPreferences Optional existing hierarchy to merge the new hierarchies into. + * + * @return The root hierarchy (if one was not provided, the new hierarchy's root). + */ + static PreferenceScreen inflateFromIntent(PreferenceManager manager, Intent intent, PreferenceScreen screen) { + try { + Method m = PreferenceManager.class.getDeclaredMethod("inflateFromIntent", Intent.class, PreferenceScreen.class); + m.setAccessible(true); + PreferenceScreen prefScreen = (PreferenceScreen) m.invoke(manager, intent, screen); + return prefScreen; + } catch (Exception e) { + Log.w(TAG, "Couldn't call PreferenceManager.inflateFromIntent by reflection", e); + } + return null; + } + + /** + * Inflates a preference hierarchy from XML. If a preference hierarchy is given, the new + * preference hierarchies will be merged in. + * + * @param context The context of the resource. + * @param resId The resource ID of the XML to inflate. + * @param rootPreferences Optional existing hierarchy to merge the new hierarchies into. + * + * @return The root hierarchy (if one was not provided, the new hierarchy's root). + * + * @hide + */ + static PreferenceScreen inflateFromResource(PreferenceManager manager, Activity activity, int resId, PreferenceScreen screen) { + try { + Method m = PreferenceManager.class.getDeclaredMethod("inflateFromResource", Context.class, int.class, PreferenceScreen.class); + m.setAccessible(true); + PreferenceScreen prefScreen = (PreferenceScreen) m.invoke(manager, activity, resId, screen); + return prefScreen; + } catch (Exception e) { + Log.w(TAG, "Couldn't call PreferenceManager.inflateFromResource by reflection", e); + } + return null; + } + + /** + * Returns the root of the preference hierarchy managed by this class. + * + * @return The {@link PreferenceScreen} object that is at the root of the hierarchy. + */ + static PreferenceScreen getPreferenceScreen(PreferenceManager manager) { + try { + Method m = PreferenceManager.class.getDeclaredMethod("getPreferenceScreen"); + m.setAccessible(true); + return (PreferenceScreen) m.invoke(manager); + } catch (Exception e) { + Log.w(TAG, "Couldn't call PreferenceManager.getPreferenceScreen by reflection", e); + } + return null; + } + + /** + * Called by the {@link PreferenceManager} to dispatch a subactivity result. + */ + static void dispatchActivityResult(PreferenceManager manager, int requestCode, int resultCode, Intent data) { + try { + Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityResult", int.class, int.class, Intent.class); + m.setAccessible(true); + m.invoke(manager, requestCode, resultCode, data); + } catch (Exception e) { + Log.w(TAG, "Couldn't call PreferenceManager.dispatchActivityResult by reflection", e); + } + } + + /** + * Called by the {@link PreferenceManager} to dispatch the activity stop event. + */ + static void dispatchActivityStop(PreferenceManager manager) { + try { + Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityStop"); + m.setAccessible(true); + m.invoke(manager); + } catch (Exception e) { + Log.w(TAG, "Couldn't call PreferenceManager.dispatchActivityStop by reflection", e); + } + } + + /** + * Called by the {@link PreferenceManager} to dispatch the activity destroy event. + */ + static void dispatchActivityDestroy(PreferenceManager manager) { + try { + Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityDestroy"); + m.setAccessible(true); + m.invoke(manager); + } catch (Exception e) { + Log.w(TAG, "Couldn't call PreferenceManager.dispatchActivityDestroy by reflection", e); + } + } + + /** + * Sets the root of the preference hierarchy. + * + * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. + * + * @return Whether the {@link PreferenceScreen} given is different than the previous. + */ + static boolean setPreferences(PreferenceManager manager, PreferenceScreen screen) { + try { + Method m = PreferenceManager.class.getDeclaredMethod("setPreferences", PreferenceScreen.class); + m.setAccessible(true); + return ((Boolean) m.invoke(manager, screen)); + } catch (Exception e) { + Log.w(TAG, "Couldn't call PreferenceManager.setPreferences by reflection", e); + } + return false; + } + +} |