Java tutorial
/* Copyright (c) 2009 Christoph Studer <chstuder@gmail.com> * Copyright (c) 2010 Jan Berkel <jan.berkel@gmail.com> * * 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.zegoggles.smssync; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Locale; import android.annotation.TargetApi; import android.os.Build; import android.text.TextUtils; import android.util.Log; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.provider.CallLog; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import static com.zegoggles.smssync.ContactAccessor.ContactGroup; import static com.zegoggles.smssync.App.*; import org.apache.commons.codec.binary.Base64; public class PrefStore { /** * Preference key containing the maximum date of messages that were * successfully synced. */ static final String PREF_MAX_SYNCED_DATE_SMS = "max_synced_date"; static final String PREF_MAX_SYNCED_DATE_MMS = "max_synced_date_mms"; static final String PREF_MAX_SYNCED_DATE_CALLLOG = "max_synced_date_calllog"; /** Preference key containing the Google account username. */ static final String PREF_LOGIN_USER = "login_user"; /** Preference key containing the Google account password. */ static final String PREF_LOGIN_PASSWORD = "login_password"; /** Preference key containing a UID used for the threading reference header. */ static final String PREF_REFERENCE_UID = "reference_uid"; /** Preference key containing the server address */ static final String PREF_SERVER_ADDRESS = "server_address"; /** Preference key containing the server protocol */ static final String PREF_SERVER_PROTOCOL = "server_protocol"; static final String PREF_SERVER_AUTHENTICATION = "server_authentication"; static final String PREF_OAUTH_TOKEN = "oauth_token"; static final String PREF_OAUTH2_TOKEN = "oauth2_token"; static final String PREF_OAUTH_TOKEN_SECRET = "oauth_token_secret"; static final String PREF_OAUTH_USER = "oauth_user"; static final String PREF_OAUTH2_USER = "oauth2_user"; /** Preference key containing the IMAP folder name where SMS should be backed up to. */ static final String PREF_IMAP_FOLDER = "imap_folder"; /** Preference key containing the IMAP folder name where SMS should be backed up to. */ static final String PREF_IMAP_FOLDER_CALLLOG = "imap_folder_calllog"; /** Preference key containing the IMAP folder name where SMS should be backed up to. */ static final String PREF_MAIL_SUBJECT_PREFIX = "mail_subject_prefix"; /** Preference key for storing whether to enable auto sync or not. */ static final String PREF_ENABLE_AUTO_SYNC = "enable_auto_sync"; /** Preference key for the timeout between an SMS is received and the scheduled sync. */ static final String PREF_INCOMING_TIMEOUT_SECONDS = "auto_backup_incoming_schedule"; /** Preference key for the interval between backup of outgoing SMS. */ static final String PREF_REGULAR_TIMEOUT_SECONDS = "auto_backup_schedule"; /** Preference for storing the maximum items per sync. */ static final String PREF_MAX_ITEMS_PER_SYNC = "max_items_per_sync"; /** Preference for storing the maximum items per restore. */ static final String PREF_MAX_ITEMS_PER_RESTORE = "max_items_per_restore"; /** Preference for storing the maximum items per restore. */ static final String PREF_RESTORE_STARRED_ONLY = "restore_starred_only"; /** Preference for storing whether backed up messages should be marked as read on Gmail. */ static final String PREF_MARK_AS_READ = "mark_as_read"; /** Preference for storing whether restored messages should be marked as read. */ static final String PREF_MARK_AS_READ_ON_RESTORE = "mark_as_read_on_restore"; static final String PREF_EMAIL_ADDRESS_STYLE = "email_address_style"; static final String PREF_BACKUP_SMS = "backup_sms"; static final String PREF_RESTORE_SMS = "restore_sms"; static final String PREF_BACKUP_MMS = "backup_mms"; static final String PREF_BACKUP_CALLLOG = "backup_calllog"; static final String PREF_RESTORE_CALLLOG = "restore_calllog"; static final String PREF_CALLLOG_SYNC_CALENDAR = "backup_calllog_sync_calendar"; static final String PREF_CALLLOG_SYNC_CALENDAR_ENABLED = "backup_calllog_sync_calendar_enabled"; static final String PREF_CALLLOG_TYPES = "backup_calllog_types"; static final String PREF_BACKUP_CONTACT_GROUP = "backup_contact_group"; static final String PREF_CONNECTED = "connected"; static final String PREF_WIFI_ONLY = "wifi_only"; static final String PREF_THIRD_PARTY_INTEGRATION = "third_party_integration"; static final String PREF_APP_LOG = "app_log"; /** Default value for {@link PrefStore#PREF_MAX_SYNCED_DATE_SMS}. */ static final long DEFAULT_MAX_SYNCED_DATE = -1; /** Default value for {@link PrefStore#PREF_IMAP_FOLDER}. */ static final String DEFAULT_IMAP_FOLDER = "SMS"; /** Default value for {@link PrefStore#PREF_IMAP_FOLDER_CALLLOG}. */ static final String DEFAULT_IMAP_FOLDER_CALLLOG = "Call log"; /** Default value for {@link PrefStore#PREF_MAIL_SUBJECT_PREFIX}. */ static final boolean DEFAULT_MAIL_SUBJECT_PREFIX = false; /** Default value for {@link PrefStore#PREF_ENABLE_AUTO_SYNC}. */ static final boolean DEFAULT_ENABLE_AUTO_SYNC = false; /** Default value for {@link PrefStore#PREF_INCOMING_TIMEOUT_SECONDS}. */ static final int DEFAULT_INCOMING_TIMEOUT_SECONDS = 60 * 3; /** Default value for {@link PrefStore#PREF_REGULAR_TIMEOUT_SECONDS}. */ static final int DEFAULT_REGULAR_TIMEOUT_SECONDS = 2 * 60 * 60; // 2h /** Default value for {@link #PREF_MAX_ITEMS_PER_SYNC}. */ static final int DEFAULT_MAX_ITEMS_PER_SYNC = -1; static final int DEFAULT_MAX_ITEMS_PER_RESTORE = -1; /** Default value for {@link #PREF_MARK_AS_READ}. */ static final boolean DEFAULT_MARK_AS_READ = true; static final boolean DEFAULT_MARK_AS_READ_ON_RESTORE = true; /** Default value for {@link #PREF_SERVER_ADDRESS}. */ static final String DEFAULT_SERVER_ADDRESS = "imap.gmail.com:993"; /** Default value for {@link #PREF_SERVER_PROTOCOL}. */ static final String DEFAULT_SERVER_PROTOCOL = "+ssl+"; public static boolean isAppLogEnabled(Context ctx) { return getPrefs(ctx).getBoolean(PREF_APP_LOG, false); } enum AuthMode { PLAIN, XOAUTH } enum CallLogTypes { EVERYTHING, MISSED, INCOMING, OUTGOING, INCOMING_OUTGOING } public enum AddressStyle { NAME, NAME_AND_NUMBER, NUMBER } static SharedPreferences getPrefs(Context ctx) { return PreferenceManager.getDefaultSharedPreferences(ctx); } // All sensitive information is stored in a separate prefs file so we can // backup the rest without exposing sensitive data static SharedPreferences getCredentials(Context ctx) { return ctx.getSharedPreferences("credentials", Context.MODE_PRIVATE); } static long getMostRecentSyncedDate(Context ctx) { return Math.max(Math.max(getMaxSyncedDateSms(ctx), getMaxSyncedDateMms(ctx) * 1000), getMaxSyncedDateCallLog(ctx)); } static long getMaxSyncedDateSms(Context ctx) { return getPrefs(ctx).getLong(PREF_MAX_SYNCED_DATE_SMS, DEFAULT_MAX_SYNCED_DATE); } static long getMaxSyncedDateMms(Context ctx) { return getPrefs(ctx).getLong(PREF_MAX_SYNCED_DATE_MMS, DEFAULT_MAX_SYNCED_DATE); } static long getMaxSyncedDateCallLog(Context ctx) { return getPrefs(ctx).getLong(PREF_MAX_SYNCED_DATE_CALLLOG, DEFAULT_MAX_SYNCED_DATE); } static void setMaxSyncedDateSms(Context ctx, long maxSyncedDate) { getPrefs(ctx).edit().putLong(PREF_MAX_SYNCED_DATE_SMS, maxSyncedDate).commit(); } static void setMaxSyncedDateMms(Context ctx, long maxSyncedDate) { getPrefs(ctx).edit().putLong(PREF_MAX_SYNCED_DATE_MMS, maxSyncedDate).commit(); } static void setMaxSyncedDateCallLog(Context ctx, long maxSyncedDate) { getPrefs(ctx).edit().putLong(PREF_MAX_SYNCED_DATE_CALLLOG, maxSyncedDate).commit(); } static String getImapUsername(Context ctx) { return getPrefs(ctx).getString(PREF_LOGIN_USER, null); } static String getImapPassword(Context ctx) { return getCredentials(ctx).getString(PREF_LOGIN_PASSWORD, null); } static void setImapPassword(Context ctx, String s) { getCredentials(ctx).edit().putString(PREF_LOGIN_PASSWORD, s).commit(); } static XOAuthConsumer getOAuthConsumer(Context ctx) { return new XOAuthConsumer(getOauthUsername(ctx), getOauthToken(ctx), getOauthTokenSecret(ctx)); } static String getOauthToken(Context ctx) { return getCredentials(ctx).getString(PREF_OAUTH_TOKEN, null); } static String getOauth2Token(Context ctx) { return getCredentials(ctx).getString(PREF_OAUTH2_TOKEN, null); } static String getOauthTokenSecret(Context ctx) { return getCredentials(ctx).getString(PREF_OAUTH_TOKEN_SECRET, null); } static boolean hasOauthTokens(Context ctx) { return getOauthUsername(ctx) != null && getOauthToken(ctx) != null && getOauthTokenSecret(ctx) != null; } static boolean hasOAuth2Tokens(Context ctx) { return getOauth2Username(ctx) != null && getOauth2Token(ctx) != null; } private static String getOauthUsername(Context ctx) { return getPrefs(ctx).getString(PREF_OAUTH_USER, null); } private static String getOauth2Username(Context ctx) { return getPrefs(ctx).getString(PREF_OAUTH2_USER, null); } public static String getUsername(Context ctx) { return getPrefs(ctx).getString(PREF_OAUTH_USER, getOauth2Username(ctx)); } static void setOauthUsername(Context ctx, String s) { getPrefs(ctx).edit().putString(PREF_OAUTH_USER, s).commit(); } static void setOauthTokens(Context ctx, String token, String secret) { getCredentials(ctx).edit().putString(PREF_OAUTH_TOKEN, token).putString(PREF_OAUTH_TOKEN_SECRET, secret) .commit(); } static void setOauth2Token(Context ctx, String username, String token) { getPrefs(ctx).edit().putString(PREF_OAUTH2_USER, username).commit(); getCredentials(ctx).edit().putString(PREF_OAUTH2_TOKEN, token).commit(); } static AuthMode getAuthMode(Context ctx) { return getDefaultType(ctx, PREF_SERVER_AUTHENTICATION, AuthMode.class, AuthMode.XOAUTH); } static ContactGroup getBackupContactGroup(Context ctx) { return new ContactGroup(getStringAsInt(ctx, PREF_BACKUP_CONTACT_GROUP, -1)); } static boolean useXOAuth(Context ctx) { return getAuthMode(ctx) == AuthMode.XOAUTH && isGmail(ctx); } static String getUserEmail(Context ctx) { switch (getAuthMode(ctx)) { case XOAUTH: return getUsername(ctx); default: return getImapUsername(ctx); } } static boolean isLoginInformationSet(Context ctx) { switch (getAuthMode(ctx)) { case PLAIN: return !TextUtils.isEmpty(getImapPassword(ctx)) && !TextUtils.isEmpty(getImapUsername(ctx)); case XOAUTH: return hasOauthTokens(ctx) || hasOAuth2Tokens(ctx); default: return false; } } static boolean isSmsBackupEnabled(Context ctx) { return getPrefs(ctx).getBoolean(PREF_BACKUP_SMS, true); } static boolean isMmsBackupEnabled(Context ctx) { final int version = android.os.Build.VERSION.SDK_INT; return version >= SmsSync.MIN_VERSION_MMS && getPrefs(ctx).getBoolean(PREF_BACKUP_MMS, false); } static boolean isCallLogBackupEnabled(Context ctx) { return getPrefs(ctx).getBoolean(PREF_BACKUP_CALLLOG, false); } static boolean isCallLogCalendarSyncEnabled(Context ctx) { return getCallLogCalendarId(ctx) >= 0 && getPrefs(ctx).getBoolean(PREF_CALLLOG_SYNC_CALENDAR_ENABLED, false); } static <T extends Enum<T>> T getDefaultType(Context ctx, String pref, Class<T> tClazz, T defaultType) { try { final String s = getPrefs(ctx).getString(pref, null); return s == null ? defaultType : Enum.valueOf(tClazz, s.toUpperCase(Locale.ENGLISH)); } catch (IllegalArgumentException e) { Log.e(TAG, "getDefaultType(" + pref + ")", e); return defaultType; } } static CallLogTypes getCallLogType(Context ctx) { return getDefaultType(ctx, PREF_CALLLOG_TYPES, CallLogTypes.class, CallLogTypes.EVERYTHING); } static boolean isCallLogTypeEnabled(Context ctx, int type) { switch (getCallLogType(ctx)) { case OUTGOING: return type == CallLog.Calls.OUTGOING_TYPE; case INCOMING: return type == CallLog.Calls.INCOMING_TYPE; case MISSED: return type == CallLog.Calls.MISSED_TYPE; case INCOMING_OUTGOING: return type != CallLog.Calls.MISSED_TYPE; default: return true; } } static int getCallLogCalendarId(Context ctx) { return getStringAsInt(ctx, PREF_CALLLOG_SYNC_CALENDAR, -1); } static boolean isRestoreStarredOnly(Context ctx) { return getPrefs(ctx).getBoolean(PREF_RESTORE_STARRED_ONLY, false); } static boolean isRestoreSms(Context ctx) { return getPrefs(ctx).getBoolean(PREF_RESTORE_SMS, true); } static boolean isRestoreCallLog(Context ctx) { return getPrefs(ctx).getBoolean(PREF_RESTORE_CALLLOG, true); } static String getReferenceUid(Context ctx) { return getPrefs(ctx).getString(PREF_REFERENCE_UID, null); } static void setReferenceUid(Context ctx, String referenceUid) { getPrefs(ctx).edit().putString(PREF_REFERENCE_UID, referenceUid).commit(); } static String getImapFolder(Context ctx) { return getPrefs(ctx).getString(PREF_IMAP_FOLDER, DEFAULT_IMAP_FOLDER); } static String getCallLogFolder(Context ctx) { return getPrefs(ctx).getString(PREF_IMAP_FOLDER_CALLLOG, DEFAULT_IMAP_FOLDER_CALLLOG); } static boolean getMailSubjectPrefix(Context ctx) { return getPrefs(ctx).getBoolean(PREF_MAIL_SUBJECT_PREFIX, DEFAULT_MAIL_SUBJECT_PREFIX); } static int getMaxItemsPerSync(Context ctx) { return getStringAsInt(ctx, PREF_MAX_ITEMS_PER_SYNC, DEFAULT_MAX_ITEMS_PER_SYNC); } static int getMaxItemsPerRestore(Context ctx) { return getStringAsInt(ctx, PREF_MAX_ITEMS_PER_RESTORE, DEFAULT_MAX_ITEMS_PER_RESTORE); } static AddressStyle getEmailAddressStyle(Context ctx) { return getDefaultType(ctx, PREF_EMAIL_ADDRESS_STYLE, AddressStyle.class, AddressStyle.NAME); } static boolean isWifiOnly(Context ctx) { return (getPrefs(ctx).getBoolean(PREF_WIFI_ONLY, false)); } static boolean isAllow3rdPartyIntegration(Context ctx) { return (getPrefs(ctx).getBoolean(PREF_THIRD_PARTY_INTEGRATION, false)); } private static int getStringAsInt(Context ctx, String key, int def) { try { String s = getPrefs(ctx).getString(key, null); if (s == null) return def; return Integer.valueOf(s); } catch (NumberFormatException e) { return def; } } /** * @param imapFolder the folder * @return whether an IMAP folder is valid. */ static boolean isValidImapFolder(String imapFolder) { return !(imapFolder == null || imapFolder.length() == 0) && !(imapFolder.charAt(0) == ' ' || imapFolder.charAt(imapFolder.length() - 1) == ' '); } static boolean isEnableAutoSync(Context ctx) { return getPrefs(ctx).getBoolean(PREF_ENABLE_AUTO_SYNC, DEFAULT_ENABLE_AUTO_SYNC); } static int getIncomingTimeoutSecs(Context ctx) { return getStringAsInt(ctx, PREF_INCOMING_TIMEOUT_SECONDS, DEFAULT_INCOMING_TIMEOUT_SECONDS); } static int getRegularTimeoutSecs(Context ctx) { return getStringAsInt(ctx, PREF_REGULAR_TIMEOUT_SECONDS, DEFAULT_REGULAR_TIMEOUT_SECONDS); } static boolean getMarkAsRead(Context ctx) { return getPrefs(ctx).getBoolean(PREF_MARK_AS_READ, DEFAULT_MARK_AS_READ); } static boolean getMarkAsReadOnRestore(Context ctx) { return getPrefs(ctx).getBoolean(PREF_MARK_AS_READ_ON_RESTORE, DEFAULT_MARK_AS_READ_ON_RESTORE); } static boolean isFirstSync(Context ctx) { return !getPrefs(ctx).contains(PREF_MAX_SYNCED_DATE_SMS); } static boolean isFirstUse(Context ctx) { final String key = "first_use"; if (isFirstSync(ctx) && !getPrefs(ctx).contains(key)) { getPrefs(ctx).edit().putBoolean(key, false).commit(); return true; } else { return false; } } static void clearOauthData(Context ctx) { final String oauth2token = getOauth2Token(ctx); getPrefs(ctx).edit().remove(PREF_OAUTH_USER).remove(PREF_OAUTH2_USER).commit(); getCredentials(ctx).edit().remove(PREF_OAUTH_TOKEN).remove(PREF_OAUTH_TOKEN_SECRET) .remove(PREF_OAUTH2_TOKEN).commit(); if (!TextUtils.isEmpty(oauth2token) && Integer.parseInt(Build.VERSION.SDK) >= 5) { AccountManagerAuthActivity.invalidateToken(ctx, oauth2token); } } static void clearLastSyncData(Context ctx) { getPrefs(ctx).edit().remove(PREF_MAX_SYNCED_DATE_SMS).remove(PREF_MAX_SYNCED_DATE_MMS) .remove(PREF_MAX_SYNCED_DATE_CALLLOG).commit(); } static boolean isNotificationEnabled(Context ctx) { return getPrefs(ctx).getBoolean("notifications", false); } static boolean confirmAction(Context ctx) { return getPrefs(ctx).getBoolean("confirm_action", false); } static String getServerAddress(Context ctx) { return getPrefs(ctx).getString(PREF_SERVER_ADDRESS, DEFAULT_SERVER_ADDRESS); } static String getServerProtocol(Context ctx) { return getPrefs(ctx).getString(PREF_SERVER_PROTOCOL, DEFAULT_SERVER_PROTOCOL); } static boolean isGmail(Context ctx) { return "imap.gmail.com:993".equalsIgnoreCase(getServerAddress(ctx)); } static String encode(String s) { return s == null ? "" : URLEncoder.encode(s); } static String getStoreUri(Context ctx) { if (useXOAuth(ctx)) { if (hasOauthTokens(ctx)) { XOAuthConsumer consumer = getOAuthConsumer(ctx); return String.format(Consts.IMAP_URI, DEFAULT_SERVER_PROTOCOL, "xoauth:" + encode(consumer.getUsername()), encode(consumer.generateXOAuthString()), getServerAddress(ctx)); } else if (hasOAuth2Tokens(ctx)) { return String.format(Consts.IMAP_URI, DEFAULT_SERVER_PROTOCOL, "xoauth2:" + encode(getOauth2Username(ctx)), encode(generateXOAuth2Token(ctx)), getServerAddress(ctx)); } else { Log.w(TAG, "No valid xoauth1/2 tokens"); return null; } } else { return String.format(Consts.IMAP_URI, getServerProtocol(ctx), encode(getImapUsername(ctx)), encode(getImapPassword(ctx)).replace("+", "%20"), getServerAddress(ctx)); } } /** * <p> * The SASL XOAUTH2 initial client response has the following format: * </p> * <code>base64("user="{User}"^Aauth=Bearer "{Access Token}"^A^A")</code> * <p> * For example, before base64-encoding, the initial client response might look like this: * </p> * <code>user=someuser@example.com^Aauth=Bearer vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==^A^A</code> * <p/> * <em>Note:</em> ^A represents a Control+A (\001). * @see <a href="https://developers.google.com/google-apps/gmail/xoauth2_protocol#the_sasl_xoauth2_mechanism"> * The SASL XOAUTH2 Mechanism</a> */ private static String generateXOAuth2Token(Context context) { final String username = getOauth2Username(context); final String token = getOauth2Token(context); final String formatted = "user=" + username + "\001auth=Bearer " + token + "\001\001"; try { return new String(Base64.encodeBase64(formatted.getBytes("UTF-8")), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } static String getVersion(Context context, boolean code) { android.content.pm.PackageInfo pInfo; try { pInfo = context.getPackageManager().getPackageInfo(SmsSync.class.getPackage().getName(), PackageManager.GET_META_DATA); return "" + (code ? pInfo.versionCode : pInfo.versionName); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "error", e); return null; } } @TargetApi(8) static boolean isInstalledOnSDCard(Context context) { android.content.pm.PackageInfo pInfo; try { pInfo = context.getPackageManager().getPackageInfo(SmsSync.class.getPackage().getName(), PackageManager.GET_META_DATA); return (pInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "error", e); return false; } } static boolean showUpgradeMessage(Context ctx) { final String key = "upgrade_message_seen"; boolean seen = getPrefs(ctx).getBoolean(key, false); if (!seen && isOldSmsBackupInstalled(ctx)) { getPrefs(ctx).edit().putBoolean(key, true).commit(); return true; } else { return false; } } static boolean isOldSmsBackupInstalled(Context context) { try { context.getPackageManager().getPackageInfo("tv.studer.smssync", android.content.pm.PackageManager.GET_META_DATA); return true; } catch (android.content.pm.PackageManager.NameNotFoundException e) { return false; } } // move credentials from default shared prefs to new separate prefs static boolean upgradeCredentials(Context ctx) { final String flag = "upgraded_credentials"; SharedPreferences prefs = getPrefs(ctx); boolean upgraded = prefs.getBoolean(flag, false); if (!upgraded) { Log.d(TAG, "Upgrading credentials"); SharedPreferences creds = getCredentials(ctx); SharedPreferences.Editor prefsEditor = prefs.edit(); SharedPreferences.Editor credsEditor = creds.edit(); for (String field : new String[] { PREF_OAUTH_TOKEN, PREF_OAUTH_TOKEN_SECRET, PREF_LOGIN_PASSWORD }) { if (prefs.getString(field, null) != null && creds.getString(field, null) == null) { if (LOCAL_LOGV) Log.v(TAG, "Moving credential " + field); credsEditor.putString(field, prefs.getString(field, null)); prefsEditor.remove(field); } else if (LOCAL_LOGV) Log.v(TAG, "Skipping field " + field); } boolean success = false; if (credsEditor.commit()) { prefsEditor.putBoolean(flag, true); success = prefsEditor.commit(); } return success; } else { return false; } } }