/*
* Copyright 2014 Sebastiano Poggi and Francesco Pontillo
*
* 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 net.frakbot.FWeather.activity;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.backup.BackupManager;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.*;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockPreferenceActivity;
import net.frakbot.FWeather.R;
import net.frakbot.FWeather.fragments.*;
import net.frakbot.FWeather.util.TrackerHelper;
import net.frakbot.FWeather.util.WeatherLocationPreference;
import net.frakbot.FWeather.util.WidgetHelper;
import net.frakbot.global.Const;
import net.frakbot.util.feedback.FeedbackHelper;
import net.frakbot.util.log.FLog;
import org.jraf.android.backport.switchwidget.SwitchPreference;
import java.util.List;
/**
* A {@link android.preference.PreferenceActivity} that presents a set of application settings. On
* handset devices, settings are presented as a single list. On tablets,
* settings are split by category, with category headers shown to the left of
* the list of settings.
* <p/>
* See <a href="http://developer.android.com/design/patterns/settings.html">
* Android Design: Settings</a> for design guidelines and the <a
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
* API Guide</a> for more information on developing a Settings UI.
*/
public class SettingsActivity extends SherlockPreferenceActivity implements
OnSharedPreferenceChangeListener {
/**
* Determines whether to always show the simplified settings UI, where
* settings are presented in a single list. When false, settings are shown
* as a master/detail two-pane view on tablets. When true, a single pane is
* shown on tablets.
*/
private static final boolean ALWAYS_SIMPLE_PREFS = false;
private static final String TAG = SettingsActivity.class.getSimpleName();
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
private Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = null;
private int mNewWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
if (intent != null
&& AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(intent.getAction())) {
mNewWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
// See http://code.google.com/p/android/issues/detail?id=2539
setResult(RESULT_CANCELED, new Intent()
.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mNewWidgetId));
}
mHandler = new Handler();
}
@Override
protected void onStart() {
super.onStart();
TrackerHelper.activityStart(this);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sets and shows the title in the ActionBar
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
setupActionBar(actionBar);
}
setupSimplePreferencesScreen();
}
private void setupActionBar(ActionBar actionBar) {
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowHomeEnabled(false);
actionBar.setDisplayShowCustomEnabled(true);
Button btnDone = (Button) getLayoutInflater().inflate(R.layout.include_ab_done, null);
btnDone.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mNewWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
setResult(RESULT_OK, new Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mNewWidgetId));
}
finish();
}
});
actionBar.setCustomView(btnDone);
}
@SuppressWarnings("deprecation")
@Override
protected void onPause() {
super.onPause();
// Unregister the SharedPreferences listener (me, duh)
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
protected void onStop() {
super.onStop();
// Update the widgets after the configuration is closed
FLog.d(this, "SettingsActivity", "Closing the settings Activity; updating widgets");
requestWidgetsUpdate(true, true);
TrackerHelper.activityStop(this);
}
@SuppressWarnings("deprecation")
@Override
protected void onResume() {
super.onResume();
// Register a SharedPreferences listener (me, duh)
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
protected boolean isValidFragment(String fragmentName) {
// We only accept our own fragments to use, or those provided by the super
return AdvancedPreferencesFragment.class.getName().equals(fragmentName) ||
BackupPreferenceFragment.class.getName().equals(fragmentName) ||
CustomizationPreferencesFragment.class.getName().equals(fragmentName) ||
DataSyncPreferencesFragment.class.getName().equals(fragmentName) ||
InformationPreferencesFragment.class.getName().equals(fragmentName) ||
super.isValidFragment(fragmentName);
}
/**
* This gets called whenever a SharedPreference changes;
* then, it notifies the BackupManager that something has changed.
* {@inheritDoc}
*/
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
new BackupManager(this).dataChanged();
}
/**
* Shows the simplified settings UI if the device configuration if the
* device configuration dictates that a simplified, single-pane UI should be
* shown.
*/
@SuppressWarnings("deprecation")
private void setupSimplePreferencesScreen() {
if (!isSimplePreferences(this)) {
return;
}
// In the simplified UI, fragments are not used at all and we instead
// use the older PreferenceActivity APIs.
// Add 'general' preferences.
PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(this);
setPreferenceScreen(screen);
// Add 'customization' preferences, and a corresponding header.
PreferenceCategory fakeHeader = new PreferenceCategory(this);
fakeHeader.setTitle(R.string.pref_header_customization);
screen.addPreference(fakeHeader);
addPreferencesFromResource(R.xml.pref_customization);
// Add 'data and sync' preferences, and a corresponding header.
fakeHeader = new PreferenceCategory(this);
fakeHeader.setTitle(R.string.pref_header_data_sync);
screen.addPreference(fakeHeader);
addPreferencesFromResource(R.xml.pref_data_sync);
setupRefreshNowOnClickListener(findPreference(getString(R.string.pref_key_sync_force)));
// Add 'advanced' preferences, and a corresponding header.
fakeHeader = new PreferenceCategory(this);
fakeHeader.setTitle(R.string.pref_header_advanced);
screen.addPreference(fakeHeader);
addPreferencesFromResource(R.xml.pref_advanced);
setupAnalyticsOnChangeListener((SwitchPreference) findPreference(getString(R.string.pref_key_analytics)));
// Add 'info' preferences, and a corresponding header.
fakeHeader = new PreferenceCategory(this);
fakeHeader.setTitle(R.string.pref_header_info);
screen.addPreference(fakeHeader);
addPreferencesFromResource(R.xml.pref_info);
setupFeedbackOnClickListener(findPreference(getString(R.string.pref_key_feedback)));
setupChangelogOnClickListener(findPreference(getString(R.string.pref_key_changelog)));
// Bind the summaries of EditText/List/Dialog/Ringtone preferences to
// their values. When their values change, their summaries are updated
// to reflect the new value, per the Android Design guidelines.
bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_key_sync_frequency)));
bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_key_ui_override_language)));
bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_key_ui_bgopacity)));
bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_key_weather_location)));
}
/**
* Sets up the OnClick listener for the refresh now preference.
*
* @param preference The refresh now preference
*/
private void setupRefreshNowOnClickListener(Preference preference) {
preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
FLog.i(SettingsActivity.this, TAG, "Forcing weather update (user request)");
requestWidgetsUpdate(true);
return true;
}
});
}
/**
* Requests an update of all the widgets we currently have.
*
* @param forced True if this is a forced update request, false otherwise
*/
private void requestWidgetsUpdate(boolean forced) {
requestWidgetsUpdate(forced, false);
}
/**
* Requests an update of all the widgets we currently have. It can optionally
* also be silent (no UI).
*
* @param forced True if this is a forced update request, false otherwise
* @param silent True if this is a silent forced update request, false otherwise
*/
private void requestWidgetsUpdate(boolean forced, boolean silent) {
Intent i = WidgetHelper.getUpdaterIntent(this, forced, silent);
startService(i);
}
/**
* Sets up the OnClick listener for the feedback preference.
* Inspired by http://blog.tomtasche.at/2012/10/use-built-in-feedback-mechanism-on.html
*
* @param preference The feedback preference
*/
public void setupFeedbackOnClickListener(Preference preference) {
preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(final Preference preference) {
FLog.i(SettingsActivity.this, TAG, "Sending feedback");
FeedbackHelper.sendFeedback(SettingsActivity.this);
preference.setEnabled(false);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
preference.setEnabled(true);
}
}, 5000);
return true;
}
});
}
/**
* Handles the preference change by requesting the TrackerHelper to send an event.
*
* @param preference The changed preference
* @param newValue The new value
*/
private void handlePreferenceChange(Preference preference, Object newValue) {
Long value = (long) 0;
if (preference.getKey().equals(Const.Preferences.ANALYTICS)) {
if (newValue == Boolean.FALSE) {
value = (long) 0;
} else {
value = (long) 1;
}
} else if (preference.getKey().equals(Const.Preferences.SYNC_FREQUENCY)) {
value = Long.valueOf((String) newValue);
}
TrackerHelper.preferenceChange(this, preference.getKey(), value);
}
/**
* Builds the listener for the preference changes.
*/
private void buildListener() {
if (sBindPreferenceSummaryToValueListener != null) {
FLog.v(TAG, "buildListener() won't do anything because the listener already exists");
return;
}
sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();
if (preference.getKey().equals(Const.Preferences.SYNC_FREQUENCY)) {
SharedPreferences prefs = preference.getSharedPreferences();
// If the old value differs from the new value
if (!prefs.getString(Const.Preferences.SYNC_FREQUENCY, "-1").equals(stringValue)) {
sendSyncPreferenceChangedBroadcast();
// Handle the generic preference change
handlePreferenceChange(preference, value);
}
}
if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);
// Set the summary to reflect the new value.
preference.setSummary(index >= 0 ? listPreference.getEntries()[index] : null);
} else if (preference instanceof WeatherLocationPreference) {
preference.setSummary(
WeatherLocationPreference.getDisplayValue(preference.getContext(), stringValue));
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
}
};
}
/**
* Sets up the Changelog preference click listener.
*
* @param preference The Changelog preference.
*/
public void setupChangelogOnClickListener(Preference preference) {
preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
AlertDialog.Builder b = new AlertDialog.Builder(SettingsActivity.this/*,
R.style.Theme_FWeather_Settings_Dialog*/);
b.setTitle(R.string.pref_title_changelog)
.setView(getLayoutInflater().inflate(R.layout.dialog_changelog, null))
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
b.show();
return true;
}
});
}
/**
* Sets up the Analytics preference listener.
*
* @param preference The Analytics preference.
*/
public void setupAnalyticsOnChangeListener(SwitchPreference preference) {
preference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(final Preference preference, Object newValue) {
// Handle the generic preference change
handlePreferenceChange(preference, newValue);
if (newValue == Boolean.FALSE) {
AlertDialog.Builder b = new AlertDialog.Builder(SettingsActivity.this);
b.setMessage(R.string.analytics_disable_warning)
.setPositiveButton(android.R.string.ok, null);
AlertDialog dialog = b.create();
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
Toast.makeText(SettingsActivity.this, R.string.analytics_disabled,
Toast.LENGTH_SHORT)
.show();
}
});
dialog.show();
FLog.i(SettingsActivity.this, TAG, "Disabled Google Analytics");
return true;
} else {
Toast.makeText(SettingsActivity.this, R.string.analytics_enabled_thanks, Toast.LENGTH_SHORT)
.show();
FLog.i(SettingsActivity.this, TAG, "Enabled Google Analytics");
return true;
}
}
});
}
/**
* {@inheritDoc}
*/
@Override
public boolean onIsMultiPane() {
return isXLargeTablet(this) && !isSimplePreferences(this);
}
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
/**
* Determines whether the simplified settings UI should be shown. This is
* true if this is forced via {@link #ALWAYS_SIMPLE_PREFS}, or the device
* doesn't have newer APIs like {@link android.preference.PreferenceFragment}, or the device
* doesn't have an extra-large screen. In these cases, a single-pane
* "simplified" settings UI should be shown.
*/
@SuppressWarnings({"ConstantConditions", "PointlessBooleanExpression"})
private static boolean isSimplePreferences(Context context) {
return ALWAYS_SIMPLE_PREFS
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
|| !isXLargeTablet(context);
}
/**
* {@inheritDoc}
*/
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<PreferenceActivity.Header> target) {
if (!isSimplePreferences(this)) {
loadHeadersFromResource(R.xml.pref_headers, target);
}
}
/**
* Binds a preference's summary to its value. More specifically, when the
* preference's value is changed, its summary (line of text below the
* preference title) is updated to reflect the value. The summary is also
* immediately updated upon calling this method. The exact display format is
* dependent on the type of preference.
*
* @see #sBindPreferenceSummaryToValueListener
*/
public void bindPreferenceSummaryToValue(Preference preference) {
if (sBindPreferenceSummaryToValueListener == null) {
FLog.i(TAG, "Preference summary listener isn't initialized, creating it now");
buildListener();
}
// Set the listener to watch for value changes.
preference
.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
// Trigger the listener immediately with the preference's
// current value.
sBindPreferenceSummaryToValueListener.onPreferenceChange(
preference,
PreferenceManager.getDefaultSharedPreferences(
preference.getContext()).getString(preference.getKey(), ""));
}
/**
* Broadcasts an action that causes the update of the updater alarm.
* If the preference changes, the update rate of the application data
* will change too, and a new update will be requested.
*/
private void sendSyncPreferenceChangedBroadcast() {
Intent i = new Intent(Const.Intents.SYNC_RATE_PREFERENCE_CHANGED_ACTION);
sendBroadcast(i);
}
}