org.dmfs.webcal.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.dmfs.webcal.MainActivity.java

Source

/*
 * Copyright (C) 2014 SchedJoules
 *
 * 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.dmfs.webcal;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

import org.dmfs.android.calendarcontent.provider.CalendarContentContract;
import org.dmfs.android.calendarcontent.provider.CalendarContentContract.SubscriptionId;
import org.dmfs.android.calendarcontent.secrets.ISecretProvider;
import org.dmfs.android.calendarcontent.secrets.SecretProvider;
import org.dmfs.android.retentionmagic.annotations.Retain;
import org.dmfs.webcal.fragments.CalendarItemFragment;
import org.dmfs.webcal.fragments.CategoriesListFragment.CategoryNavigator;
import org.dmfs.webcal.fragments.GenericListFragment;
import org.dmfs.webcal.fragments.PagerFragment;
import org.dmfs.webcal.utils.billing.IabHelper;
import org.dmfs.webcal.utils.billing.IabHelper.OnIabPurchaseFinishedListener;
import org.dmfs.webcal.utils.billing.IabHelper.OnIabSetupFinishedListener;
import org.dmfs.webcal.utils.billing.IabHelper.QueryInventoryFinishedListener;
import org.dmfs.webcal.utils.billing.IabResult;
import org.dmfs.webcal.utils.billing.Inventory;
import org.dmfs.webcal.utils.billing.Purchase;

import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log;

import com.schedjoules.analytics.Analytics;
import com.schedjoules.analytics.PurchaseState;
import com.schedjoules.android.sdk.utils.PushHelper;

/**
 * The Home Activity is used to display the main page along with the subsections.
 */
public class MainActivity extends NavbarActivity implements CategoryNavigator, IBillingActivity,
        OnIabSetupFinishedListener, QueryInventoryFinishedListener, LoaderCallbacks<Cursor> {
    private final static int REQUEST_CODE_LAUNCH_PURCHASE_FLOW = 10003;
    private final static int REQUEST_CODE_LAUNCH_SUBSCRIPTION_FLOW = 10004;

    /**
     * The interval in milliseconds to retry to load the inventory in case of an error.
     **/
    private static final long RELOAD_INVENTORY_INTERVAL = 15000;

    private final static long MAX_ANALYTICS_AGE = 60L * 1000L; // 1 minute

    public static final String SUBCATEGORY_EXTRA = "org.dmfs.webcal.SUBCATEGORY_EXTRA";

    private FragmentManager mFragmentManager;

    /**
     * List of callbacks for inventory results.
     */
    private final List<WeakReference<OnInventoryListener>> mBillingCallbacks = Collections
            .synchronizedList(new ArrayList<WeakReference<OnInventoryListener>>(8));

    private final List<String> mMoreItemSkus = Collections.synchronizedList(new ArrayList<String>());

    /**
     * Helper for billing services.
     */
    private IabHelper mIabHelper;

    private boolean mIabHelperReady = false;

    private Inventory mInventoryCache = null;
    @Retain
    private long mSelectedItemId = 0;

    @Retain(key = "firststart", permanent = true, classNS = "MainActivity")
    private boolean mFirstStart = true;

    private final Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        setupNavbar();

        mFragmentManager = getSupportFragmentManager();

        if (savedInstanceState == null) {
            selectItem(R.id.side_nav_all_calendars);
            setNavigationSelection(0);
        } else if (mSelectedItemId >= 0) {
            selectItem(mSelectedItemId);
            setNavigationSelectionById(mSelectedItemId);
        }

        if (mFirstStart) {
            mFirstStart = false;
            openDrawer();
        }

        if (savedInstanceState == null) {
            Analytics.sessionStart("MAIN");
        }

        SharedPreferences prefs = getSharedPreferences(getPackageName() + "_preferences", 0);

        if (prefs.getBoolean("enable_analytics", true)) {
            Analytics.enable();
        } else {
            Analytics.disable();
        }

        PushHelper.registerPush(this);

        LoaderManager lm = getSupportLoaderManager();
        lm.initLoader(-2, null, this);

        initIabHelper();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mHandler.postDelayed(mAnalyticsTrigger, MAX_ANALYTICS_AGE);
    }

    @Override
    protected void onPause() {
        super.onPause();
        mHandler.removeCallbacks(mAnalyticsTrigger);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mIabHelper != null) {
            try {
                mIabHelper.dispose();
            } catch (Exception e) {
                // ignore, it seems to throw an exception every now and then in
                // the emulator
            }
            mIabHelper = null;
            mIabHelperReady = false;
        }
        Analytics.sessionEnd();
    }

    /*
     * (non-Javadoc)
     * 
     * Forward onActivityResult to mIabHelper;
     * 
     * @see android.support.v4.app.FragmentActivity#onActivityResult(int, int, android.content.Intent)
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (mIabHelper != null) {
            mIabHelper.handleActivityResult(requestCode, resultCode, data);
        }
    }

    @Override
    protected String getActivityTitle() {
        Fragment contentFragment = mFragmentManager.findFragmentById(R.id.content);
        Bundle args = contentFragment.getArguments();
        return args != null ? args.getString(PagerFragment.ARG_PAGE_TITLE) : "";
    }

    @Override
    public void openCategory(long id, String title, long icon) {
        mFragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

        PagerFragment fragment = PagerFragment.newInstance(this, id, title, icon);
        fragmentTransaction.replace(R.id.content, fragment);
        fragmentTransaction.addToBackStack("");
        fragmentTransaction.commit();
        mSelectedItemId = -1;
        setNavigationSelection(-1);
    }

    @Override
    public void openCalendar(long id, long icon) {
        mFragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
        Fragment contentFragment = mFragmentManager.findFragmentById(R.id.content);
        String title = contentFragment.getArguments().getString(PagerFragment.ARG_PAGE_TITLE);
        CalendarItemFragment fragment = CalendarItemFragment.newInstance(this, id, title, icon);
        fragmentTransaction.replace(R.id.content, fragment);
        fragmentTransaction.addToBackStack("");
        fragmentTransaction.commit();
        mSelectedItemId = -1;
        setNavigationSelection(-1);
    }

    @Override
    protected boolean selectItem(long id) {
        if (id == mSelectedItemId) {
            super.selectItem(id);
            return true;
        }

        Fragment fragment = null;
        if (id == R.id.side_nav_favorite_calendars) {
            fragment = GenericListFragment.newInstance(
                    CalendarContentContract.ContentItem.getStarredItemsContentUri(this),
                    getString(R.string.side_nav_favorite_calendars), R.string.error_favorite_calendars_empty,
                    GenericListFragment.PROJECTION2, true);
            mSelectedItemId = id;
            Analytics.event("fav-calendars", "menu", null, null, null, null);
        } else if (id == R.id.side_nav_my_calendars) {
            fragment = GenericListFragment.newInstance(
                    CalendarContentContract.SubscribedCalendars.getContentUri(this),
                    getString(R.string.side_nav_my_calendars), R.string.error_my_calendars_empty,
                    GenericListFragment.PROJECTION, true);

            mSelectedItemId = id;
            Analytics.event("my-calendars", "menu", null, null, null, null);
        } else if (id == R.id.side_nav_all_calendars) {
            fragment = PagerFragment.newInstance(this, 0, getItemTitleById(id), -1);
            mSelectedItemId = id;
            Analytics.event("all-calendars", "menu", null, null, null, null);
        } else if (id == R.id.side_nav_faq) {
            Intent faqIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.faq_url)));
            startActivity(faqIntent);
            Analytics.event("faq", "menu", null, null, null, null);
        } else if (id == R.id.side_nav_feedback) {
            Analytics.event("feedback", "menu", null, null, null, null);
            PackageManager pm = getPackageManager();
            PackageInfo pi = null;
            try {
                pi = pm.getPackageInfo(getPackageName(), 0);
            } catch (NameNotFoundException e) {
                // that should not happen!
            }

            StringBuilder emailContent = new StringBuilder(256);
            emailContent.append("\r\n\r\n\r\n\r\n\r\n--------------\r\n");
            emailContent.append("app: ").append(getPackageName()).append("\r\n");
            emailContent.append("version: ").append(pi.versionName).append(" / ").append(pi.versionCode)
                    .append("\r\n");
            emailContent.append("locale: ").append(Locale.getDefault().getLanguage()).append("\r\n");
            emailContent.append("location: ").append(Locale.getDefault().getCountry()).append("\r\n");
            emailContent.append("timezone: ").append(TimeZone.getDefault().getID()).append("\r\n");
            emailContent.append("device: ").append(android.os.Build.DEVICE).append("\r\n");
            emailContent.append("model: ").append(android.os.Build.MODEL).append("\r\n");
            emailContent.append("os version: ").append(android.os.Build.VERSION.RELEASE).append(" / ")
                    .append(android.os.Build.VERSION.SDK_INT).append("\r\n");
            emailContent.append("firmware: ").append(android.os.Build.ID).append("\r\n");

            Intent emailIntent = new Intent(Intent.ACTION_SENDTO,
                    Uri.fromParts("mailto", getString(R.string.contact_address), null));
            emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Contact");
            emailIntent.putExtra(Intent.EXTRA_TEXT, emailContent.toString());
            startActivity(Intent.createChooser(emailIntent, getString(R.string.send_email)));
            // fragment = ContactFragment.newInstance();
        } else if (id == R.id.side_nav_settings) {
            Intent preferencesIntent = new Intent(this, PreferencesActivity.class);
            startActivity(preferencesIntent);
            Analytics.event("settings", "menu", "settings clicked", null, null, null);
        }

        if (fragment != null) {
            // drop backstack
            mFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

            FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
            fragmentTransaction.replace(R.id.content, fragment);
            fragmentTransaction.commit();
        }

        super.selectItem(id);

        return fragment != null;
    }

    private void initIabHelper() {
        if (mIabHelper != null) {
            // nothing to do
            return;
        }

        // helper has not been set up yet, do that now
        mIabHelper = new IabHelper(this, SecretProvider.INSTANCE.getSecret(this, ISecretProvider.KEY_LICENSE_KEY));

        try {
            mIabHelper.startSetup(this);
        } catch (Exception e) {
            try {
                mIabHelper.dispose();
            } catch (Exception e2) {
                // ignore
            }
            mIabHelper = null;

            notifyInventoryListenersAboutError();

            // try again
            mHandler.postDelayed(mReloadInventoryRunnable, RELOAD_INVENTORY_INTERVAL);
        }
    }

    @Override
    public void onIabSetupFinished(IabResult result) {
        if (result.isSuccess()) {
            mIabHelperReady = true;

            // initialize mMoreItemSkus with subscription id if we have any
            String subscriptionId = SubscriptionId.getSubscriptionId(this);
            if (subscriptionId != null) {
                mMoreItemSkus.add(subscriptionId);
            }

            refreshInventory();
        } else {
            notifyInventoryListenersAboutError();
        }
    }

    @Override
    public void onQueryInventoryFinished(IabResult result, Inventory inv) {
        if (result.isSuccess()) {
            mInventoryCache = inv;
            notifyInventoryListeners();
        } else {
            Log.e(TAG, "can't load inventory " + result.getMessage());
            notifyInventoryListenersAboutError();

            // try again
            mHandler.postDelayed(mReloadInventoryRunnable, RELOAD_INVENTORY_INTERVAL);
        }
    }

    /**
     * A {@link Runnable} that triggers a transmission of analytics hits at least every {@value #MAX_INVENTORY_AGE} milliseconds.
     * 
     * @see #MAX_ANALYTICS_AGE
     */
    private final Runnable mAnalyticsTrigger = new Runnable() {

        @Override
        public void run() {
            Analytics.triggerSendBatch();
            mHandler.postDelayed(mAnalyticsTrigger, MAX_ANALYTICS_AGE);
        }
    };

    /**
     * A {@link Runnable} that triggers the inventory loading.
     * 
     */
    private final Runnable mReloadInventoryRunnable = new Runnable() {

        @Override
        public void run() {
            refreshInventory();
        }
    };

    @Override
    public void refreshInventory() {
        if (mIabHelper != null && mIabHelperReady && !mIabHelper.asyncInProgress()) {
            Log.v(TAG, "refeshing inventory");
            mIabHelper.queryInventoryAsync(true, mMoreItemSkus, this);
        }
    }

    @Override
    public Inventory getInventory() {
        return mInventoryCache;
    }

    @Override
    public synchronized void addOnInventoryListener(OnInventoryListener onInventoryListener) {
        if (onInventoryListener == null) {
            return;
        }

        for (WeakReference<OnInventoryListener> cb : mBillingCallbacks) {
            if (onInventoryListener == cb.get()) {
                // callback already in list
                return;
            }
        }

        mBillingCallbacks.add(new WeakReference<OnInventoryListener>(onInventoryListener));
    }

    @Override
    public synchronized void removeOnInventoryListener(OnInventoryListener onInventoryListener) {
        if (onInventoryListener == null) {
            return;
        }

        for (WeakReference<OnInventoryListener> cb : mBillingCallbacks) {
            if (onInventoryListener == cb.get()) {
                mBillingCallbacks.remove(cb);
                return;
            }
        }
    }

    @Override
    public boolean requestSkuData(String sku) {
        if (!mMoreItemSkus.contains(sku)) {
            mMoreItemSkus.add(sku);
            return true;
        }
        return false;
    }

    @Override
    public void purchase(final String productId, final OnIabPurchaseFinishedListener callback) {
        if (mIabHelper != null && mIabHelperReady && !mIabHelper.asyncInProgress()) {
            Analytics.purchase(null, PurchaseState.STARTED, 0.0f, null, null, productId);
            // we inject our own listener to update the inventory
            mIabHelper.launchPurchaseFlow(this, productId, REQUEST_CODE_LAUNCH_PURCHASE_FLOW,
                    new OnIabPurchaseFinishedListener() {
                        @Override
                        public void onIabPurchaseFinished(IabResult result, Purchase info) {
                            if (result.isSuccess()) {
                                Analytics.purchase(info.getOrderId(), PurchaseState.SUCCEEDED, 0.0f, null, null,
                                        productId);
                                // update inventory
                                refreshInventory();
                            } else if (result.isFailure()) {
                                Analytics.purchase(null, PurchaseState.FAILED, 0.0f, null, result.getMessage(),
                                        productId);
                            }

                            // forward result
                            callback.onIabPurchaseFinished(result, info);
                        }
                    });
        }
    }

    @Override
    public void subscribe(final String subscriptionId, final OnIabPurchaseFinishedListener callback) {
        if (mIabHelper != null && mIabHelperReady && !mIabHelper.asyncInProgress()) {
            Analytics.purchase(null, PurchaseState.STARTED, 0.0f, null, null, subscriptionId);
            // we inject our own listener to update the inventory
            mIabHelper.launchSubscriptionPurchaseFlow(this, subscriptionId, REQUEST_CODE_LAUNCH_SUBSCRIPTION_FLOW,
                    new OnIabPurchaseFinishedListener() {
                        @Override
                        public void onIabPurchaseFinished(IabResult result, Purchase info) {
                            if (result.isSuccess()) {
                                Analytics.purchase(info.getOrderId(), PurchaseState.SUCCEEDED, 0.0f, null, null,
                                        subscriptionId);
                                // update inventory
                                refreshInventory();
                            } else if (result.isFailure()) {
                                Analytics.purchase(null, PurchaseState.FAILED, 0.0f, null, result.getMessage(),
                                        subscriptionId);
                            }

                            // forward result
                            callback.onIabPurchaseFinished(result, info);
                        }
                    });
        }
    }

    /**
     * Notify all listeners about a new inventory.
     */
    private synchronized void notifyInventoryListeners() {
        if (mBillingCallbacks != null) {
            List<WeakReference<OnInventoryListener>> listenerRefs = new ArrayList<WeakReference<OnInventoryListener>>(
                    mBillingCallbacks);

            for (WeakReference<OnInventoryListener> listenerRef : listenerRefs) {
                OnInventoryListener callback = listenerRef.get();

                if (callback != null) {
                    callback.onInventoryLoaded();
                } else {
                    mBillingCallbacks.remove(listenerRef);
                }
            }
        }

    }

    /**
     * Notify all listeners about an error.
     */
    private synchronized void notifyInventoryListenersAboutError() {
        if (mBillingCallbacks != null) {
            List<WeakReference<OnInventoryListener>> listenerRefs = new ArrayList<WeakReference<OnInventoryListener>>(
                    mBillingCallbacks);

            for (WeakReference<OnInventoryListener> listenerRef : listenerRefs) {
                OnInventoryListener callback = listenerRef.get();

                if (callback != null) {
                    callback.onInventoryError();
                } else {
                    mBillingCallbacks.remove(listenerRef);
                }
            }
        }

    }

    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle extras) {
        return new CursorLoader(this, SubscriptionId.getContentUri(this), null, null, null, null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        if (cursor != null && cursor.moveToFirst()) {
            String subscriptionId = cursor.getString(1);
            if (subscriptionId != null && !mMoreItemSkus.contains(subscriptionId)) {
                mMoreItemSkus.add(subscriptionId);
                refreshInventory();
            }
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> arg0) {
    }
}