diff --git a/CREDITS.markdown b/CREDITS.markdown
index cd961562..cd6e645b 100644
--- a/CREDITS.markdown
+++ b/CREDITS.markdown
@@ -43,6 +43,8 @@ the alternative [Git logo](http://henrik.nyh.se/2007/06/alternative-git-logo-and
* Bernd Hirschler - [German Translation](https://github.com/rtyley/agit/pull/35)
+* Scott Johnson - [Sync Frequency UI](https://github.com/rtyley/agit/pull/103)
+
* Eddie Ringle - [Tweak GitHub url parsing](https://github.com/rtyley/agit/pull/29)
* Leonardo Taglialegne - [Italian Translation](https://github.com/rtyley/agit/pull/46)
diff --git a/agit/AndroidManifest.xml b/agit/AndroidManifest.xml
index fcef7206..f09f1c72 100644
--- a/agit/AndroidManifest.xml
+++ b/agit/AndroidManifest.xml
@@ -15,6 +15,12 @@
+
+
+
+
+
+
@@ -142,6 +148,12 @@
+
+
+
+
+
+
@@ -184,4 +196,5 @@
+
\ No newline at end of file
diff --git a/agit/res/layout/settings_activity.xml b/agit/res/layout/settings_activity.xml
new file mode 100644
index 00000000..eddecee5
--- /dev/null
+++ b/agit/res/layout/settings_activity.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/agit/res/menu/dashboard.xml b/agit/res/menu/dashboard.xml
index a79d1556..37617f69 100644
--- a/agit/res/menu/dashboard.xml
+++ b/agit/res/menu/dashboard.xml
@@ -10,6 +10,11 @@
android:title="@string/open_menu_option"
android:showAsAction="never">
+ -
+
- SSH Install Guide
Clone...
Clone...
+ Settings...
Url
Target Folder
Repository url
@@ -112,4 +113,46 @@
"No viewer available for '%1$s'"
+
+ Settings
+ Sync Settings
+ Sync Frequency
+ Tap to set how often repository synchronization will occur
+ setting_sync_frequency
+
+ 0
+ Sync daily at
+ setting_sync_frequency_daily_hour
+ setting_sync_frequency_daily_min
+ setting_sync_frequency_subtitle
+
+
+
+
- Manually
+ - Schedule daily at…
+ - Every minute
+ - Every 5 minutes
+ - Every 15 minutes
+ - Every hour
+ - Every 4 hours
+ - Every day
+ - Every week
+
+
+
+
+ - -1
+ - @string/setting_sync_frequency_daily
+ - 1
+ - 5
+ - 15
+ - 60
+ - 240
+ - 1440
+ - 10080
+
diff --git a/agit/src/main/java/com/madgag/agit/DashboardActivity.java b/agit/src/main/java/com/madgag/agit/DashboardActivity.java
index 014a0a20..5f992b89 100644
--- a/agit/src/main/java/com/madgag/agit/DashboardActivity.java
+++ b/agit/src/main/java/com/madgag/agit/DashboardActivity.java
@@ -43,7 +43,6 @@
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.github.rtyley.android.sherlock.roboguice.activity.RoboSherlockFragmentActivity;
-import com.madgag.android.IntentUtil;
import com.madgag.android.util.store.InstallAppDialogFragment;
import java.io.File;
@@ -81,6 +80,9 @@ public boolean onOptionsItemSelected(MenuItem item) {
case R.id.clone:
startActivity(new Intent(this, CloneLauncherActivity.class));
return true;
+ case R.id.settings:
+ startActivity(new Intent(this, SettingsActivity.class));
+ return true;
case R.id.open_repo:
if (isIntentAvailable(this, PICK_DIRECTORY_INTENT)) {
Intent intent = new Intent(PICK_DIRECTORY_INTENT);
diff --git a/agit/src/main/java/com/madgag/agit/SettingsActivity.java b/agit/src/main/java/com/madgag/agit/SettingsActivity.java
new file mode 100644
index 00000000..d9fdbbe8
--- /dev/null
+++ b/agit/src/main/java/com/madgag/agit/SettingsActivity.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2011, 2012 Roberto Tyley
+ *
+ * This file is part of 'Agit' - an Android Git client.
+ *
+ * Agit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Agit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/ .
+ */
+
+package com.madgag.agit;
+
+import static com.madgag.agit.sync.AccountAuthenticatorService.addAccount;
+import android.app.TimePickerDialog;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.widget.TimePicker;
+
+import com.madgag.agit.sync.SyncRepoManager;
+
+public class SettingsActivity extends PreferenceActivity {
+ public void onCreate(Bundle aSavedInstanceState) {
+ super.onCreate(aSavedInstanceState);
+ addPreferencesFromResource(R.layout.settings_activity);
+ ListPreference syncFreq = (ListPreference) findPreference(getString(R.string.setting_sync_frequency_key));
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsActivity.this);
+ setSummary();
+ syncFreq.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object o) {
+ String value = (String)o;
+ final SharedPreferences.Editor edtr = prefs.edit();
+
+ if (value.equals(getString(R.string.setting_sync_frequency_daily))) {
+ // Check if our previous value was the same thing, so we can set it in the time
+ // picker dialog.
+ int hourDefault = 12;
+ int minDefault = 0;
+ String prevVal = prefs.getString(getString(R.string.setting_sync_frequency_key), "-1");
+ if (prevVal.equals(o)) {
+ hourDefault = prefs.getInt(getString(R.string.setting_sync_frequency_daily_hour_key), hourDefault);
+ minDefault = prefs.getInt(getString(R.string.setting_sync_frequency_daily_min_key), minDefault);
+ }
+
+ TimePickerDialog.OnTimeSetListener timeListener = new TimePickerDialog.OnTimeSetListener() {
+ @Override
+ public void onTimeSet(TimePicker timePicker, int hourOfDay, int minOfHour) {
+ String subTitle = "Sync daily at " + hourOfDay + ":" + (minOfHour < 10 ? "0" : "") + minOfHour;
+ edtr.putInt(getString(R.string.setting_sync_frequency_daily_hour_key), hourOfDay);
+ edtr.putInt(getString(R.string.setting_sync_frequency_daily_min_key), minOfHour);
+ edtr.putString(getString(R.string.setting_sync_frequency_subtitle_key), subTitle);
+ edtr.commit();
+
+ SyncRepoManager manager = new SyncRepoManager();
+ manager.setDailySync(SettingsActivity.this, hourOfDay, minOfHour);
+ SettingsActivity.this.setSummary();
+ }
+ };
+
+ // Display new dialog with the available options.
+ TimePickerDialog timePicker = new TimePickerDialog(SettingsActivity.this, timeListener, hourDefault, minDefault, false);
+ timePicker.setTitle(getString(R.string.setting_sync_frequency_daily_title));
+ timePicker.show();
+ } else {
+ // Loop to find out what the index of the chosen value is in the array - it should be the same
+ // as the chosen value in the array of possible choices (see comment in strings.xml).
+ String[] indexArray = getResources().getStringArray(R.array.setting_sync_choices_values);
+ String[] subtitleArray = getResources().getStringArray(R.array.setting_sync_choices);
+ int subtitleIndex = 0;
+ for (String s : indexArray) {
+ if (s.equals(o)) {
+ break;
+ }
+ subtitleIndex++;
+ }
+
+ String subTitle = subtitleArray[subtitleIndex];
+ edtr.putString(getString(R.string.setting_sync_frequency_subtitle_key), subTitle);
+ edtr.commit();
+ SyncRepoManager manager = new SyncRepoManager();
+ manager.cancelDailySync(SettingsActivity.this);
+ }
+
+ SettingsActivity.this.setSummary();
+
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ try {
+ addAccount(this);
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to re-add account for syncing after preference changes", e);
+ }
+ }
+
+ /**
+ * Displays the value stored in the preference setting_sync_frequency_subtitle_key in the summary line of the
+ * list preference, so the user doesn't have to click through to view which option is selected.
+ */
+ private void setSummary() {
+ ListPreference syncFreq = (ListPreference) findPreference(getString(R.string.setting_sync_frequency_key));
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsActivity.this);
+ syncFreq.setSummary(prefs.getString(getString(R.string.setting_sync_frequency_subtitle_key),
+ getString(R.string.setting_instruction_sync_frequency)));
+ }
+
+ private static final String TAG = "SettingsActivity";
+}
diff --git a/agit/src/main/java/com/madgag/agit/sync/AccountAuthenticatorService.java b/agit/src/main/java/com/madgag/agit/sync/AccountAuthenticatorService.java
index c25f7c6e..143ac443 100644
--- a/agit/src/main/java/com/madgag/agit/sync/AccountAuthenticatorService.java
+++ b/agit/src/main/java/com/madgag/agit/sync/AccountAuthenticatorService.java
@@ -34,8 +34,10 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
+import android.preference.PreferenceManager;
import android.util.Log;
/**
@@ -71,15 +73,28 @@ public static Bundle addAccount(Context ctx) {
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
}
- configureSyncFor(account);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
+ int syncFreq = Integer.parseInt(prefs.getString("setting_sync_frequency", "-1"));
+ configureSyncFor(account, syncFreq);
+
return result;
}
- private static void configureSyncFor(Account account) {
- Log.d(TAG, "Trying to configure account for sync...");
- setIsSyncable(account, AGIT_PROVIDER_AUTHORITY, 1);
- setSyncAutomatically(account, AGIT_PROVIDER_AUTHORITY, true);
- ContentResolver.addPeriodicSync(account, AGIT_PROVIDER_AUTHORITY, new Bundle(), (long) (15 * 60));
+ private static void configureSyncFor(Account aAccount, int aSyncFreq) {
+ // There are three possible sync settings we could have:
+ // < 0: Indicates sync is disabled
+ // == 0: Indicates sync is enabled, but it's handled by an alarm because it's a time-of-day sync
+ // > 0: Indicates sync should be enabled for aSyncFreq minutes
+ if (aSyncFreq < 0) {
+ Log.d(TAG, "Disabling sync settings");
+ setIsSyncable(aAccount, AGIT_PROVIDER_AUTHORITY, 0);
+ ContentResolver.removePeriodicSync(aAccount, AGIT_PROVIDER_AUTHORITY, new Bundle());
+ } else {
+ Log.d(TAG, "Trying to configure account for sync at rate of " + aSyncFreq + " minutes");
+ setIsSyncable(aAccount, AGIT_PROVIDER_AUTHORITY, 1);
+ setSyncAutomatically(aAccount, AGIT_PROVIDER_AUTHORITY, true);
+ ContentResolver.addPeriodicSync(aAccount, AGIT_PROVIDER_AUTHORITY, new Bundle(), (long) (aSyncFreq * 60));
+ }
}
private static class AccountAuthenticatorImpl extends AbstractAccountAuthenticator {
diff --git a/agit/src/main/java/com/madgag/agit/sync/SyncAdapter.java b/agit/src/main/java/com/madgag/agit/sync/SyncAdapter.java
index b74251e9..5ec2de74 100644
--- a/agit/src/main/java/com/madgag/agit/sync/SyncAdapter.java
+++ b/agit/src/main/java/com/madgag/agit/sync/SyncAdapter.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
+import android.util.Log;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -50,6 +51,7 @@ public SyncAdapter(Context context) {
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
SyncResult syncResult) {
+ Log.d(TAG, "Performing sync");
cancelAnyCurrentCampaign();
contextScope.enter(getContext());
try {
diff --git a/agit/src/main/java/com/madgag/agit/sync/SyncRepoManager.java b/agit/src/main/java/com/madgag/agit/sync/SyncRepoManager.java
new file mode 100644
index 00000000..90431be0
--- /dev/null
+++ b/agit/src/main/java/com/madgag/agit/sync/SyncRepoManager.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2011, 2012 Roberto Tyley
+ *
+ * This file is part of 'Agit' - an Android Git client.
+ *
+ * Agit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Agit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/ .
+ */
+
+package com.madgag.agit.sync;
+
+import static com.madgag.agit.sync.Constants.AGIT_ACCOUNT_NAME;
+import static com.madgag.agit.sync.Constants.AGIT_ACCOUNT_TYPE;
+import static com.madgag.agit.sync.Constants.AGIT_PROVIDER_AUTHORITY;
+import android.accounts.Account;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Manager class for daily synchronization settings. Because periodic sync at time-of-day intervals is handled
+ * differently than periodic sync on a regular schedule, we use this class as a receiver of alerts on a regular
+ * interval from AlarmManager.
+ */
+public class SyncRepoManager extends BroadcastReceiver {
+
+ public SyncRepoManager() {
+
+ }
+
+ /**
+ * Setup a sync at a specific hour and minute of the day. Every effort is made to make the sync happen at the time
+ * of day specified, but it's not guaranteed. Because we use inexact repeating alarms, it will never happen before
+ * the specified time, but it may happen quite a while afterwards. This is because the Android operating system
+ * tries to phase alarms so they don't happen at the same time.
+ *
+ * @param aContext The context from which the daily sync is being set up.
+ * @param aHour The hour of the day (from 0 to 24) at which the sync should happen
+ * @param aMin The minute of the hour at which the sync should happen.
+ */
+ public static void setDailySync(Context aContext, int aHour, int aMin) {
+ AlarmManager mgr =(AlarmManager)aContext.getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent("com.madgag.agit.sync.SET_DAILY_SYNC");
+ PendingIntent pend = PendingIntent.getBroadcast(aContext, 0, intent, 0);
+
+ Calendar updateTime = Calendar.getInstance();
+ updateTime.setTimeZone(TimeZone.getDefault());
+ updateTime.set(Calendar.HOUR_OF_DAY, aHour);
+ updateTime.set(Calendar.MINUTE, aMin);
+ long msTimeToRepeat = updateTime.getTimeInMillis();
+
+ mgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, msTimeToRepeat, AlarmManager.INTERVAL_DAY, pend);
+ }
+
+ /**
+ * Cancel a daily sync already set up.
+ *
+ * @param aContext The context from which the daily sync cancellation is being requested.
+ */
+ public static void cancelDailySync(Context aContext) {
+ Log.d(TAG, "Canceling daily sync");
+ AlarmManager mgr =(AlarmManager)aContext.getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent("com.madgag.agit.sync.SET_DAILY_SYNC");
+ PendingIntent pend = PendingIntent.getBroadcast(aContext, 0, intent, 0);
+ mgr.cancel(pend);
+ }
+
+ @Override
+ public void onReceive(Context aContext, Intent aIntent) {
+ Calendar rightNow = Calendar.getInstance();
+ long offset = rightNow.get(Calendar.ZONE_OFFSET) +
+ rightNow.get(Calendar.DST_OFFSET);
+ long sinceMidnight = (rightNow.getTimeInMillis() + offset) %
+ (24 * 60 * 60 * 1000);
+ int hoursSinceMidnight = (int)(sinceMidnight / (60*60*1000));
+ boolean isPM = hoursSinceMidnight > 12;
+ int clockHour = hoursSinceMidnight % 12;
+ int minutesSinceMidnight = (int)((sinceMidnight % (hoursSinceMidnight * (60 * 60 * 1000))) / (60*1000));
+
+ Log.d(TAG, "onReceive called at " + clockHour + ":" + minutesSinceMidnight + " " + (isPM ? "pm" : "am"));
+ doSyncNow();
+ }
+
+ /**
+ * Triggers a manual synchronization of the repositories. This the main event that is performed
+ * after onReceive() is seen.
+ */
+ private void doSyncNow() {
+ Bundle settingsBundle = new Bundle();
+ settingsBundle.putBoolean(
+ ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ settingsBundle.putBoolean(
+ ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+
+ Account account = new Account(AGIT_ACCOUNT_NAME, AGIT_ACCOUNT_TYPE);
+ ContentResolver.requestSync(account, AGIT_PROVIDER_AUTHORITY, settingsBundle);
+ }
+
+ private static final String TAG = "SyncRepoManager";
+}