com.zegoggles.smssync.activity.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.zegoggles.smssync.activity.MainActivity.java

Source

/* 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.activity;

import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.Telephony.Sms;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManager.BackStackEntry;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
import android.support.v7.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback;
import android.support.v7.preference.PreferenceScreen;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.squareup.otto.Subscribe;
import com.zegoggles.smssync.App;
import com.zegoggles.smssync.R;
import com.zegoggles.smssync.activity.Dialogs.SmsDefaultPackage;
import com.zegoggles.smssync.activity.Dialogs.WebConnect;
import com.zegoggles.smssync.activity.auth.AccountManagerAuthActivity;
import com.zegoggles.smssync.activity.auth.OAuth2WebAuthActivity;
import com.zegoggles.smssync.activity.events.AccountAddedEvent;
import com.zegoggles.smssync.activity.events.AccountConnectionChangedEvent;
import com.zegoggles.smssync.activity.events.FallbackAuthEvent;
import com.zegoggles.smssync.activity.events.MissingPermissionsEvent;
import com.zegoggles.smssync.activity.events.PerformAction;
import com.zegoggles.smssync.activity.events.PerformAction.Actions;
import com.zegoggles.smssync.activity.events.ThemeChangedEvent;
import com.zegoggles.smssync.activity.fragments.MainSettings;
import com.zegoggles.smssync.auth.OAuth2Client;
import com.zegoggles.smssync.preferences.AuthPreferences;
import com.zegoggles.smssync.preferences.Preferences;
import com.zegoggles.smssync.service.BackupType;
import com.zegoggles.smssync.service.SmsBackupService;
import com.zegoggles.smssync.service.SmsRestoreService;
import com.zegoggles.smssync.service.state.BackupState;
import com.zegoggles.smssync.service.state.RestoreState;
import com.zegoggles.smssync.tasks.OAuth2CallbackTask;
import com.zegoggles.smssync.utils.BundleBuilder;

import java.util.Arrays;
import java.util.List;

import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.provider.Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT;
import static android.provider.Telephony.Sms.Intents.EXTRA_PACKAGE_NAME;
import static android.support.v7.preference.PreferenceFragmentCompat.ARG_PREFERENCE_ROOT;
import static android.widget.Toast.LENGTH_LONG;
import static com.zegoggles.smssync.App.LOCAL_LOGV;
import static com.zegoggles.smssync.App.TAG;
import static com.zegoggles.smssync.App.post;
import static com.zegoggles.smssync.activity.AppPermission.allGranted;
import static com.zegoggles.smssync.activity.Dialogs.ConfirmAction.ACTION;
import static com.zegoggles.smssync.activity.Dialogs.FirstSync.MAX_ITEMS_PER_SYNC;
import static com.zegoggles.smssync.activity.Dialogs.MissingCredentials.USE_XOAUTH;
import static com.zegoggles.smssync.activity.Dialogs.Type.ABOUT;
import static com.zegoggles.smssync.activity.Dialogs.Type.ACCOUNT_MANAGER_TOKEN_ERROR;
import static com.zegoggles.smssync.activity.Dialogs.Type.CONFIRM_ACTION;
import static com.zegoggles.smssync.activity.Dialogs.Type.DISCONNECT;
import static com.zegoggles.smssync.activity.Dialogs.Type.FIRST_SYNC;
import static com.zegoggles.smssync.activity.Dialogs.Type.MISSING_CREDENTIALS;
import static com.zegoggles.smssync.activity.Dialogs.Type.OAUTH2_ACCESS_TOKEN_ERROR;
import static com.zegoggles.smssync.activity.Dialogs.Type.OAUTH2_ACCESS_TOKEN_PROGRESS;
import static com.zegoggles.smssync.activity.Dialogs.Type.RESET;
import static com.zegoggles.smssync.activity.Dialogs.Type.SMS_DEFAULT_PACKAGE_CHANGE;
import static com.zegoggles.smssync.activity.Dialogs.Type.VIEW_LOG;
import static com.zegoggles.smssync.activity.Dialogs.Type.WEB_CONNECT;
import static com.zegoggles.smssync.activity.auth.AccountManagerAuthActivity.ACTION_ADD_ACCOUNT;
import static com.zegoggles.smssync.activity.auth.AccountManagerAuthActivity.ACTION_FALLBACK_AUTH;
import static com.zegoggles.smssync.activity.auth.AccountManagerAuthActivity.EXTRA_ACCOUNT;
import static com.zegoggles.smssync.activity.auth.AccountManagerAuthActivity.EXTRA_TOKEN;
import static com.zegoggles.smssync.activity.events.PerformAction.Actions.Backup;
import static com.zegoggles.smssync.compat.SmsReceiver.isSmsBackupDefaultSmsApp;
import static com.zegoggles.smssync.service.BackupType.MANUAL;
import static com.zegoggles.smssync.service.BackupType.SKIP;

/**
 * This is the main activity showing the status of the SMS Sync service and
 * providing controls to configure it.
 */
public class MainActivity extends ThemeActivity implements OnPreferenceStartFragmentCallback,
        OnPreferenceStartScreenCallback, FragmentManager.OnBackStackChangedListener {
    static final int REQUEST_CHANGE_DEFAULT_SMS_PACKAGE = 1;
    private static final int REQUEST_PICK_ACCOUNT = 2;
    static final int REQUEST_WEB_AUTH = 3;
    private static final int REQUEST_PERMISSIONS_BACKUP_MANUAL = 4;
    private static final int REQUEST_PERMISSIONS_BACKUP_MANUAL_SKIP = 5;
    private static final int REQUEST_PERMISSIONS_BACKUP_SERVICE = 6;

    public static final String EXTRA_PERMISSIONS = "permissions";
    private static final String SCREEN_TITLE_RES = "titleRes";

    private Preferences preferences;
    private AuthPreferences authPreferences;
    private OAuth2Client oauth2Client;
    private Intent fallbackAuthIntent;
    private Intent changeDefaultPackageIntent;
    private PreferenceTitles preferenceTitles;

    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportFragmentManager().addOnBackStackChangedListener(this);

        authPreferences = new AuthPreferences(this);
        oauth2Client = new OAuth2Client(authPreferences.getOAuth2ClientId());
        fallbackAuthIntent = new Intent(this, OAuth2WebAuthActivity.class).setData(oauth2Client.requestUrl());
        changeDefaultPackageIntent = new Intent(ACTION_CHANGE_DEFAULT).putExtra(EXTRA_PACKAGE_NAME,
                getPackageName());
        preferenceTitles = new PreferenceTitles(getResources(), R.xml.preferences);
        preferences = new Preferences(this);
        if (bundle == null) {
            showFragment(new MainSettings(), null);
        }

        if (preferences.shouldShowAboutDialog()) {
            showDialog(ABOUT);
        }
        checkDefaultSmsApp();
        requestPermissionsIfNeeded();
    }

    @Override
    protected void onStart() {
        super.onStart();
        App.register(this);
    }

    @Override
    protected void onStop() {
        App.unregister(this);
        super.onStop();
    }

    @Override
    public boolean onSupportNavigateUp() {
        onBackPressed();
        return true;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_about:
            showDialog(ABOUT);
            return true;
        case R.id.menu_reset:
            showDialog(RESET);
            return true;
        case R.id.menu_view_log:
            showDialog(VIEW_LOG);

        default:
            return super.onOptionsItemSelected(item);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data + ")");

        switch (requestCode) {
        case REQUEST_CHANGE_DEFAULT_SMS_PACKAGE: {
            if (resultCode == RESULT_CANCELED)
                break;
            preferences.setSeenSmsDefaultPackageChangeDialog();
            if (preferences.getSmsDefaultPackage() != null) {
                startRestore();
            }
            break;
        }
        case REQUEST_WEB_AUTH: {
            if (resultCode == RESULT_CANCELED) {
                Toast.makeText(this, R.string.ui_dialog_access_token_error_msg, LENGTH_LONG).show();
                return;
            }

            final String code = data == null ? null : data.getStringExtra(OAuth2WebAuthActivity.EXTRA_CODE);
            if (!TextUtils.isEmpty(code)) {
                showDialog(OAUTH2_ACCESS_TOKEN_PROGRESS);
                new OAuth2CallbackTask(oauth2Client).execute(code);
            } else {
                showDialog(OAUTH2_ACCESS_TOKEN_ERROR);
            }
            break;
        }
        case REQUEST_PICK_ACCOUNT: {
            if (resultCode == RESULT_OK && data != null) {
                if (ACTION_ADD_ACCOUNT.equals(data.getAction())) {
                    handleAccountManagerAuth(data);
                } else if (ACTION_FALLBACK_AUTH.equals(data.getAction())) {
                    handleFallbackAuth(new FallbackAuthEvent(true));
                }
            } else if (LOCAL_LOGV) {
                Log.v(TAG, "request canceled, result=" + resultCode);
            }
            break;
        }
        }
    }

    @Override
    public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference preference) {
        if (LOCAL_LOGV) {
            Log.v(TAG, "onPreferenceStartFragment(" + preference + ")");
        }
        final Fragment fragment = Fragment.instantiate(this, preference.getFragment(), new BundleBuilder()
                .putInt(SCREEN_TITLE_RES, preferenceTitles.getTitleRes(preference.getKey())).build());

        showFragment(fragment, preference.getKey());
        return true;
    }

    @Override
    public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen preference) {
        if (LOCAL_LOGV) {
            Log.v(TAG, "onPreferenceStartScreen(" + preference + ")");
        }
        // API level 9 compatibility
        if (preference.getFragment() == null) {
            preference.setFragment(preference.getKey());
            return onPreferenceStartFragment(caller, preference);
        } else {
            return false;
        }
    }

    @Subscribe
    public void restoreStateChanged(final RestoreState newState) {
        if (newState.isFinished() && isSmsBackupDefaultSmsApp(this)) {
            restoreDefaultSmsProvider(preferences.getSmsDefaultPackage());
        }
    }

    @Subscribe
    public void backupStateChanged(final BackupState newState) {
        if ((newState.backupType == MANUAL || newState.backupType == SKIP) && newState.isPermissionException()) {
            ActivityCompat.requestPermissions(this, newState.getMissingPermissions(),
                    newState.backupType == SKIP ? REQUEST_PERMISSIONS_BACKUP_MANUAL_SKIP
                            : REQUEST_PERMISSIONS_BACKUP_MANUAL);
        }
    }

    @Subscribe
    public void onOAuth2Callback(OAuth2CallbackTask.OAuth2CallbackEvent event) {
        if (event.valid()) {
            authPreferences.setOauth2Token(event.token.userName, event.token.accessToken, event.token.refreshToken);
            onAuthenticated();
        } else {
            showDialog(OAUTH2_ACCESS_TOKEN_ERROR);
        }
    }

    @Subscribe
    public void onConnect(AccountConnectionChangedEvent event) {
        if (event.connected) {
            startActivityForResult(new Intent(this, AccountManagerAuthActivity.class), REQUEST_PICK_ACCOUNT);
        } else {
            showDialog(DISCONNECT);
        }
    }

    @Subscribe
    public void handleFallbackAuth(FallbackAuthEvent event) {
        if (event.showDialog) {
            showDialog(WEB_CONNECT);
        } else {
            startActivityForResult(fallbackAuthIntent, REQUEST_WEB_AUTH);
        }
    }

    @Subscribe
    public void themeChangedEvent(ThemeChangedEvent event) {
        if (Build.VERSION.SDK_INT >= HONEYCOMB) {
            recreate();
        } else {
            Toast.makeText(this, R.string.theme_app_relaunch, Toast.LENGTH_SHORT).show();
            finish();
        }
    }

    @Override
    public void onBackStackChanged() {
        if (getSupportActionBar() == null)
            return;
        getSupportActionBar().setSubtitle(getCurrentTitle());
        getSupportActionBar().setDisplayHomeAsUpEnabled(getSupportFragmentManager().getBackStackEntryCount() > 0);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        onBackStackChanged();
    }

    private @StringRes int getCurrentTitle() {
        final int entryCount = getSupportFragmentManager().getBackStackEntryCount();
        if (entryCount == 0) {
            return 0;
        } else {
            final BackStackEntry entry = getSupportFragmentManager().getBackStackEntryAt(entryCount - 1);
            return entry.getBreadCrumbTitleRes();
        }
    }

    private void onAuthenticated() {
        App.post(new AccountAddedEvent());
        // Invite user to perform a backup, but only once
        if (preferences.isFirstUse()) {
            showDialog(FIRST_SYNC);
        }
    }

    @Subscribe
    public void performAction(PerformAction action) {
        if (authPreferences.isLoginInformationSet()) {
            if (action.confirm) {
                showDialog(CONFIRM_ACTION, new BundleBuilder().putString(ACTION, action.action.name()).build());
            } else if (preferences.isFirstBackup() && action.action == Backup) {
                showDialog(FIRST_SYNC);
            } else {
                doPerform(action.action);
            }
        } else {
            showDialog(MISSING_CREDENTIALS);
        }
    }

    @Subscribe
    public void doPerform(Actions action) {
        switch (action) {
        case Backup:
        case BackupSkip:
            startBackup(action == Backup ? MANUAL : SKIP);
            break;
        case Restore:
            startRestore();
            break;
        }
    }

    private void startBackup(BackupType backupType) {
        startService(new Intent(this, SmsBackupService.class).setAction(backupType.name()));
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private void startRestore() {
        final Intent intent = new Intent(this, SmsRestoreService.class);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (isSmsBackupDefaultSmsApp(this)) {
                startService(intent);
            } else {
                final String defaultSmsPackage = Sms.getDefaultSmsPackage(this);
                Log.d(TAG, "default SMS package: " + defaultSmsPackage);
                if (!TextUtils.isEmpty(defaultSmsPackage)) {
                    preferences.setSmsDefaultPackage(defaultSmsPackage);

                    if (preferences.hasSeenSmsDefaultPackageChangeDialog()) {
                        requestDefaultSmsPackageChange();
                    } else {
                        showDialog(SMS_DEFAULT_PACKAGE_CHANGE);
                    }
                } else {
                    // no default package  running on tablet?
                    Toast.makeText(this, R.string.error_no_sms_default_package, LENGTH_LONG).show();
                }
            }
        } else {
            startService(intent);
        }
    }

    private void showFragment(@NonNull Fragment fragment, @Nullable String rootKey) {
        Bundle args = fragment.getArguments() == null ? new Bundle() : fragment.getArguments();
        args.putString(ARG_PREFERENCE_ROOT, rootKey);
        fragment.setArguments(args);
        FragmentTransaction tx = getSupportFragmentManager().beginTransaction().replace(R.id.preferences_container,
                fragment, rootKey);
        if (rootKey != null) {
            tx.addToBackStack(null);
            tx.setBreadCrumbTitle(args.getInt(SCREEN_TITLE_RES));
        }
        tx.commit();
    }

    private void showDialog(Dialogs.Type dialog) {
        final Bundle arguments = new Bundle();
        switch (dialog) {
        case FIRST_SYNC:
            arguments.putInt(MAX_ITEMS_PER_SYNC, preferences.getMaxItemsPerSync());
            break;
        case MISSING_CREDENTIALS:
            arguments.putBoolean(USE_XOAUTH, authPreferences.useXOAuth());
            break;
        case WEB_CONNECT:
            arguments.putParcelable(WebConnect.INTENT, fallbackAuthIntent);
            break;
        case SMS_DEFAULT_PACKAGE_CHANGE:
            arguments.putParcelable(SmsDefaultPackage.INTENT, changeDefaultPackageIntent);
            break;
        }
        showDialog(dialog, arguments);
    }

    private void showDialog(@NonNull Dialogs.Type dialog, @Nullable Bundle args) {
        dialog.instantiate(this, args).show(getSupportFragmentManager(), dialog.name());
    }

    private void requestDefaultSmsPackageChange() {
        startActivityForResult(changeDefaultPackageIntent, REQUEST_CHANGE_DEFAULT_SMS_PACKAGE);
    }

    private void restoreDefaultSmsProvider(String smsPackage) {
        Log.d(TAG, "restoring SMS provider " + smsPackage);
        if (!TextUtils.isEmpty(smsPackage)) {
            final Intent changeDefaultIntent = new Intent(ACTION_CHANGE_DEFAULT).putExtra(EXTRA_PACKAGE_NAME,
                    smsPackage);

            startActivity(changeDefaultIntent);
        }
    }

    private void handleAccountManagerAuth(@NonNull Intent data) {
        final String token = data.getStringExtra(EXTRA_TOKEN);
        final String account = data.getStringExtra(EXTRA_ACCOUNT);
        if (!TextUtils.isEmpty(token) && !TextUtils.isEmpty(account)) {
            authPreferences.setOauth2Token(account, token, null);
            onAuthenticated();
        } else {
            String error = data.getStringExtra(AccountManagerAuthActivity.EXTRA_ERROR);
            if (!TextUtils.isEmpty(error)) {
                showDialog(ACCOUNT_MANAGER_TOKEN_ERROR);
            }
        }
    }

    private void checkDefaultSmsApp() {
        if (isSmsBackupDefaultSmsApp(this) && SmsRestoreService.isServiceIdle()) {
            restoreDefaultSmsProvider(preferences.getSmsDefaultPackage());
        }
    }

    private void requestPermissionsIfNeeded() {
        final Intent intent = getIntent();
        if (intent != null && intent.hasExtra(EXTRA_PERMISSIONS)) {
            final String[] permissions = intent.getStringArrayExtra(EXTRA_PERMISSIONS);
            Log.v(TAG, "requesting permissions " + Arrays.toString(permissions));
            ActivityCompat.requestPermissions(this, permissions, REQUEST_PERMISSIONS_BACKUP_SERVICE);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        Log.v(TAG, "onRequestPermissionsResult(" + requestCode + "," + Arrays.toString(permissions) + ","
                + Arrays.toString(grantResults));
        switch (requestCode) {
        case REQUEST_PERMISSIONS_BACKUP_MANUAL:
        case REQUEST_PERMISSIONS_BACKUP_MANUAL_SKIP:
            if (allGranted(grantResults)) {
                startBackup(requestCode == REQUEST_PERMISSIONS_BACKUP_MANUAL ? MANUAL : SKIP);
            } else {
                final List<AppPermission> missing = AppPermission.from(permissions, grantResults);
                Log.w(TAG, "not all permissions granted: " + missing);
                post(new MissingPermissionsEvent(missing));
            }
            break;
        case REQUEST_PERMISSIONS_BACKUP_SERVICE:
            if (allGranted(grantResults)) {
                startBackup(MANUAL);
            } else {
                post(new MissingPermissionsEvent(AppPermission.from(permissions, grantResults)));
            }
            break;
        }
    }
}