| /* |
| * Copyright 2014 Google Inc. All rights reserved. |
| * |
| * 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.google.samples.apps.iosched.ui; |
| |
| import android.accounts.Account; |
| import android.accounts.AccountManager; |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.AnimatorSet; |
| import android.animation.ArgbEvaluator; |
| import android.animation.ObjectAnimator; |
| import android.animation.TypeEvaluator; |
| import android.animation.ValueAnimator; |
| import android.app.AlertDialog; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.content.SyncStatusObserver; |
| import android.content.pm.PackageManager; |
| import android.graphics.Color; |
| import android.graphics.Rect; |
| import android.net.ConnectivityManager; |
| import android.net.NetworkInfo; |
| import android.net.Uri; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.preference.PreferenceManager; |
| import android.provider.Settings; |
| import android.support.v4.view.ViewCompat; |
| import android.support.v4.widget.DrawerLayout; |
| import android.support.v4.widget.SwipeRefreshLayout; |
| import android.support.v7.app.ActionBar; |
| import android.support.v7.app.ActionBarActivity; |
| import android.support.v7.widget.Toolbar; |
| import android.text.TextUtils; |
| import android.view.Gravity; |
| import android.view.LayoutInflater; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.animation.DecelerateInterpolator; |
| import android.widget.AbsListView; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| import android.widget.ListView; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import com.google.android.gcm.GCMRegistrar; |
| import com.google.android.gms.auth.GoogleAuthUtil; |
| import com.google.samples.apps.iosched.BuildConfig; |
| import com.google.samples.apps.iosched.Config; |
| import com.google.samples.apps.iosched.R; |
| import com.google.samples.apps.iosched.gcm.ServerUtilities; |
| import com.google.samples.apps.iosched.io.JSONHandler; |
| import com.google.samples.apps.iosched.provider.ScheduleContract; |
| import com.google.samples.apps.iosched.sync.ConferenceDataHandler; |
| import com.google.samples.apps.iosched.sync.SyncHelper; |
| import com.google.samples.apps.iosched.ui.debug.DebugActionRunnerActivity; |
| import com.google.samples.apps.iosched.ui.widget.MultiSwipeRefreshLayout; |
| import com.google.samples.apps.iosched.ui.widget.ScrimInsetsScrollView; |
| import com.google.samples.apps.iosched.util.AccountUtils; |
| import com.google.samples.apps.iosched.util.AnalyticsManager; |
| import com.google.samples.apps.iosched.util.HelpUtils; |
| import com.google.samples.apps.iosched.util.ImageLoader; |
| import com.google.samples.apps.iosched.util.LUtils; |
| import com.google.samples.apps.iosched.util.LoginAndAuthHelper; |
| import com.google.samples.apps.iosched.util.PlayServicesUtils; |
| import com.google.samples.apps.iosched.util.PrefUtils; |
| import com.google.samples.apps.iosched.util.RecentTasksStyler; |
| import com.google.samples.apps.iosched.util.UIUtils; |
| import com.google.samples.apps.iosched.util.WiFiUtils; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| import static com.google.samples.apps.iosched.util.LogUtils.LOGD; |
| import static com.google.samples.apps.iosched.util.LogUtils.LOGE; |
| import static com.google.samples.apps.iosched.util.LogUtils.LOGI; |
| import static com.google.samples.apps.iosched.util.LogUtils.LOGW; |
| import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; |
| |
| /** |
| * A base activity that handles common functionality in the app. This includes the |
| * navigation drawer, login and authentication, Action Bar tweaks, amongst others. |
| */ |
| public abstract class BaseActivity extends ActionBarActivity implements |
| LoginAndAuthHelper.Callbacks, |
| SharedPreferences.OnSharedPreferenceChangeListener, |
| MultiSwipeRefreshLayout.CanChildScrollUpCallback { |
| private static final String TAG = makeLogTag(BaseActivity.class); |
| |
| // the LoginAndAuthHelper handles signing in to Google Play Services and OAuth |
| private LoginAndAuthHelper mLoginAndAuthHelper; |
| |
| // Navigation drawer: |
| private DrawerLayout mDrawerLayout; |
| |
| // Helper methods for L APIs |
| private LUtils mLUtils; |
| |
| private ObjectAnimator mStatusBarColorAnimator; |
| private LinearLayout mAccountListContainer; |
| private ViewGroup mDrawerItemsListContainer; |
| private Handler mHandler; |
| |
| private ImageView mExpandAccountBoxIndicator; |
| private boolean mAccountBoxExpanded = false; |
| |
| // When set, these components will be shown/hidden in sync with the action bar |
| // to implement the "quick recall" effect (the Action Bar and the header views disappear |
| // when you scroll down a list, and reappear quickly when you scroll up). |
| private ArrayList<View> mHideableHeaderViews = new ArrayList<View>(); |
| |
| // Durations for certain animations we use: |
| private static final int HEADER_HIDE_ANIM_DURATION = 300; |
| private static final int ACCOUNT_BOX_EXPAND_ANIM_DURATION = 200; |
| |
| // symbols for navdrawer items (indices must correspond to array below). This is |
| // not a list of items that are necessarily *present* in the Nav Drawer; rather, |
| // it's a list of all possible items. |
| protected static final int NAVDRAWER_ITEM_MY_SCHEDULE = 0; |
| protected static final int NAVDRAWER_ITEM_EXPLORE = 1; |
| protected static final int NAVDRAWER_ITEM_MAP = 2; |
| protected static final int NAVDRAWER_ITEM_SOCIAL = 3; |
| protected static final int NAVDRAWER_ITEM_VIDEO_LIBRARY = 4; |
| protected static final int NAVDRAWER_ITEM_SIGN_IN = 5; |
| protected static final int NAVDRAWER_ITEM_SETTINGS = 6; |
| protected static final int NAVDRAWER_ITEM_EXPERTS_DIRECTORY = 7; |
| protected static final int NAVDRAWER_ITEM_PEOPLE_IVE_MET = 8; |
| protected static final int NAVDRAWER_ITEM_INVALID = -1; |
| protected static final int NAVDRAWER_ITEM_SEPARATOR = -2; |
| protected static final int NAVDRAWER_ITEM_SEPARATOR_SPECIAL = -3; |
| |
| // titles for navdrawer items (indices must correspond to the above) |
| private static final int[] NAVDRAWER_TITLE_RES_ID = new int[]{ |
| R.string.navdrawer_item_my_schedule, |
| R.string.navdrawer_item_explore, |
| R.string.navdrawer_item_map, |
| R.string.navdrawer_item_social, |
| R.string.navdrawer_item_video_library, |
| R.string.navdrawer_item_sign_in, |
| R.string.navdrawer_item_settings, |
| R.string.navdrawer_item_experts_directory, |
| R.string.navdrawer_item_people_ive_met |
| }; |
| |
| // icons for navdrawer items (indices must correspond to above array) |
| private static final int[] NAVDRAWER_ICON_RES_ID = new int[] { |
| R.drawable.ic_drawer_my_schedule, // My Schedule |
| R.drawable.ic_drawer_explore, // Explore |
| R.drawable.ic_drawer_map, // Map |
| R.drawable.ic_drawer_social, // Social |
| R.drawable.ic_drawer_video_library, // Video Library |
| 0, // Sign in |
| R.drawable.ic_drawer_settings, |
| R.drawable.ic_drawer_experts, |
| R.drawable.ic_drawer_people_met, |
| }; |
| |
| // delay to launch nav drawer item, to allow close animation to play |
| private static final int NAVDRAWER_LAUNCH_DELAY = 250; |
| |
| // fade in and fade out durations for the main content when switching between |
| // different Activities of the app through the Nav Drawer |
| private static final int MAIN_CONTENT_FADEOUT_DURATION = 150; |
| private static final int MAIN_CONTENT_FADEIN_DURATION = 250; |
| |
| // list of navdrawer items that were actually added to the navdrawer, in order |
| private ArrayList<Integer> mNavDrawerItems = new ArrayList<Integer>(); |
| |
| // views that correspond to each navdrawer item, null if not yet created |
| private View[] mNavDrawerItemViews = null; |
| |
| // SwipeRefreshLayout allows the user to swipe the screen down to trigger a manual refresh |
| private SwipeRefreshLayout mSwipeRefreshLayout; |
| |
| // Primary toolbar and drawer toggle |
| private Toolbar mActionBarToolbar; |
| |
| // asynctask that performs GCM registration in the backgorund |
| private AsyncTask<Void, Void, Void> mGCMRegisterTask; |
| |
| // handle to our sync observer (that notifies us about changes in our sync state) |
| private Object mSyncObserverHandle; |
| |
| // data bootstrap thread. Data bootstrap is the process of initializing the database |
| // with the data cache that ships with the app. |
| Thread mDataBootstrapThread = null; |
| |
| // variables that control the Action Bar auto hide behavior (aka "quick recall") |
| private boolean mActionBarAutoHideEnabled = false; |
| private int mActionBarAutoHideSensivity = 0; |
| private int mActionBarAutoHideMinY = 0; |
| private int mActionBarAutoHideSignal = 0; |
| private boolean mActionBarShown = true; |
| |
| // A Runnable that we should execute when the navigation drawer finishes its closing animation |
| private Runnable mDeferredOnDrawerClosedRunnable; |
| |
| private boolean mManualSyncRequest; |
| |
| private int mThemedStatusBarColor; |
| private int mNormalStatusBarColor; |
| private int mProgressBarTopWhenActionBarShown; |
| private static final TypeEvaluator ARGB_EVALUATOR = new ArgbEvaluator(); |
| private ImageLoader mImageLoader; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| AnalyticsManager.initializeAnalyticsTracker(getApplicationContext()); |
| RecentTasksStyler.styleRecentTasksEntry(this); |
| |
| PrefUtils.init(this); |
| |
| // Check if the EULA has been accepted; if not, show it. |
| if (!PrefUtils.isTosAccepted(this)) { |
| Intent intent = new Intent(this, WelcomeActivity.class); |
| startActivity(intent); |
| finish(); |
| } |
| |
| mImageLoader = new ImageLoader(this); |
| mHandler = new Handler(); |
| |
| // Enable or disable each Activity depending on the form factor. This is necessary |
| // because this app uses many implicit intents where we don't name the exact Activity |
| // in the Intent, so there should only be one enabled Activity that handles each |
| // Intent in the app. |
| UIUtils.enableDisableActivitiesByFormFactor(this); |
| |
| if (savedInstanceState == null) { |
| registerGCMClient(); |
| } |
| |
| SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); |
| sp.registerOnSharedPreferenceChangeListener(this); |
| |
| ActionBar ab = getSupportActionBar(); |
| if (ab != null) { |
| ab.setDisplayHomeAsUpEnabled(true); |
| } |
| |
| mLUtils = LUtils.getInstance(this); |
| mThemedStatusBarColor = getResources().getColor(R.color.theme_primary_dark); |
| mNormalStatusBarColor = mThemedStatusBarColor; |
| } |
| |
| private void trySetupSwipeRefresh() { |
| mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout); |
| if (mSwipeRefreshLayout != null) { |
| mSwipeRefreshLayout.setColorSchemeResources( |
| R.color.refresh_progress_1, |
| R.color.refresh_progress_2, |
| R.color.refresh_progress_3); |
| mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { |
| @Override |
| public void onRefresh() { |
| requestDataRefresh(); |
| } |
| }); |
| |
| if (mSwipeRefreshLayout instanceof MultiSwipeRefreshLayout) { |
| MultiSwipeRefreshLayout mswrl = (MultiSwipeRefreshLayout) mSwipeRefreshLayout; |
| mswrl.setCanChildScrollUpCallback(this); |
| } |
| } |
| } |
| |
| protected void setProgressBarTopWhenActionBarShown(int progressBarTopWhenActionBarShown) { |
| mProgressBarTopWhenActionBarShown = progressBarTopWhenActionBarShown; |
| updateSwipeRefreshProgressBarTop(); |
| } |
| |
| private void updateSwipeRefreshProgressBarTop() { |
| if (mSwipeRefreshLayout == null) { |
| return; |
| } |
| |
| int progressBarStartMargin = getResources().getDimensionPixelSize( |
| R.dimen.swipe_refresh_progress_bar_start_margin); |
| int progressBarEndMargin = getResources().getDimensionPixelSize( |
| R.dimen.swipe_refresh_progress_bar_end_margin); |
| int top = mActionBarShown ? mProgressBarTopWhenActionBarShown : 0; |
| mSwipeRefreshLayout.setProgressViewOffset(false, |
| top + progressBarStartMargin, top + progressBarEndMargin); |
| } |
| |
| /** |
| * Returns the navigation drawer item that corresponds to this Activity. Subclasses |
| * of BaseActivity override this to indicate what nav drawer item corresponds to them |
| * Return NAVDRAWER_ITEM_INVALID to mean that this Activity should not have a Nav Drawer. |
| */ |
| protected int getSelfNavDrawerItem() { |
| return NAVDRAWER_ITEM_INVALID; |
| } |
| |
| /** |
| * Sets up the navigation drawer as appropriate. Note that the nav drawer will be |
| * different depending on whether the attendee indicated that they are attending the |
| * event on-site vs. attending remotely. |
| */ |
| private void setupNavDrawer() { |
| // What nav drawer item should be selected? |
| int selfItem = getSelfNavDrawerItem(); |
| |
| mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); |
| if (mDrawerLayout == null) { |
| return; |
| } |
| mDrawerLayout.setStatusBarBackgroundColor( |
| getResources().getColor(R.color.theme_primary_dark)); |
| ScrimInsetsScrollView navDrawer = (ScrimInsetsScrollView) |
| mDrawerLayout.findViewById(R.id.navdrawer); |
| if (selfItem == NAVDRAWER_ITEM_INVALID) { |
| // do not show a nav drawer |
| if (navDrawer != null) { |
| ((ViewGroup) navDrawer.getParent()).removeView(navDrawer); |
| } |
| mDrawerLayout = null; |
| return; |
| } |
| |
| if (navDrawer != null) { |
| final View chosenAccountContentView = findViewById(R.id.chosen_account_content_view); |
| final View chosenAccountView = findViewById(R.id.chosen_account_view); |
| final int navDrawerChosenAccountHeight = getResources().getDimensionPixelSize( |
| R.dimen.navdrawer_chosen_account_height); |
| navDrawer.setOnInsetsCallback(new ScrimInsetsScrollView.OnInsetsCallback() { |
| @Override |
| public void onInsetsChanged(Rect insets) { |
| ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) |
| chosenAccountContentView.getLayoutParams(); |
| lp.topMargin = insets.top; |
| chosenAccountContentView.setLayoutParams(lp); |
| |
| ViewGroup.LayoutParams lp2 = chosenAccountView.getLayoutParams(); |
| lp2.height = navDrawerChosenAccountHeight + insets.top; |
| chosenAccountView.setLayoutParams(lp2); |
| } |
| }); |
| } |
| |
| if (mActionBarToolbar != null) { |
| mActionBarToolbar.setNavigationIcon(R.drawable.ic_drawer); |
| mActionBarToolbar.setNavigationOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| mDrawerLayout.openDrawer(Gravity.START); |
| } |
| }); |
| } |
| |
| mDrawerLayout.setDrawerListener(new DrawerLayout.DrawerListener() { |
| @Override |
| public void onDrawerClosed(View drawerView) { |
| // run deferred action, if we have one |
| if (mDeferredOnDrawerClosedRunnable != null) { |
| mDeferredOnDrawerClosedRunnable.run(); |
| mDeferredOnDrawerClosedRunnable = null; |
| } |
| if (mAccountBoxExpanded) { |
| mAccountBoxExpanded = false; |
| setupAccountBoxToggle(); |
| } |
| onNavDrawerStateChanged(false, false); |
| } |
| |
| @Override |
| public void onDrawerOpened(View drawerView) { |
| onNavDrawerStateChanged(true, false); |
| } |
| |
| @Override |
| public void onDrawerStateChanged(int newState) { |
| onNavDrawerStateChanged(isNavDrawerOpen(), newState != DrawerLayout.STATE_IDLE); |
| } |
| |
| @Override |
| public void onDrawerSlide(View drawerView, float slideOffset) { |
| onNavDrawerSlide(slideOffset); |
| } |
| }); |
| |
| mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, Gravity.START); |
| |
| // populate the nav drawer with the correct items |
| populateNavDrawer(); |
| |
| // When the user runs the app for the first time, we want to land them with the |
| // navigation drawer open. But just the first time. |
| if (!PrefUtils.isWelcomeDone(this)) { |
| // first run of the app starts with the nav drawer open |
| PrefUtils.markWelcomeDone(this); |
| mDrawerLayout.openDrawer(Gravity.START); |
| } |
| } |
| |
| @Override |
| public void setContentView(int layoutResID) { |
| super.setContentView(layoutResID); |
| getActionBarToolbar(); |
| } |
| |
| // Subclasses can override this for custom behavior |
| protected void onNavDrawerStateChanged(boolean isOpen, boolean isAnimating) { |
| if (mActionBarAutoHideEnabled && isOpen) { |
| autoShowOrHideActionBar(true); |
| } |
| } |
| |
| protected void onNavDrawerSlide(float offset) {} |
| |
| protected boolean isNavDrawerOpen() { |
| return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(Gravity.START); |
| } |
| |
| protected void closeNavDrawer() { |
| if (mDrawerLayout != null) { |
| mDrawerLayout.closeDrawer(Gravity.START); |
| } |
| } |
| |
| /** Populates the navigation drawer with the appropriate items. */ |
| private void populateNavDrawer() { |
| boolean attendeeAtVenue = PrefUtils.isAttendeeAtVenue(this); |
| mNavDrawerItems.clear(); |
| |
| // decide which items will appear in the nav drawer |
| if (AccountUtils.hasActiveAccount(this)) { |
| // Only logged-in users can save sessions, so if there is no active account, |
| // there is no My Schedule |
| mNavDrawerItems.add(NAVDRAWER_ITEM_MY_SCHEDULE); |
| } else { |
| // If no active account, show Sign In |
| mNavDrawerItems.add(NAVDRAWER_ITEM_SIGN_IN); |
| } |
| |
| // Explore is always shown |
| mNavDrawerItems.add(NAVDRAWER_ITEM_EXPLORE); |
| |
| // If the attendee is on-site, show Map on the nav drawer |
| if (attendeeAtVenue) { |
| mNavDrawerItems.add(NAVDRAWER_ITEM_MAP); |
| } |
| mNavDrawerItems.add(NAVDRAWER_ITEM_SEPARATOR); |
| |
| // If attendee is on-site, show the People I've Met item |
| if (attendeeAtVenue) { |
| mNavDrawerItems.add(NAVDRAWER_ITEM_PEOPLE_IVE_MET); |
| } |
| |
| // If the experts directory hasn't expired, show it |
| if (!Config.hasExpertsDirectoryExpired()) { |
| mNavDrawerItems.add(NAVDRAWER_ITEM_EXPERTS_DIRECTORY); |
| } |
| |
| // Other items that are always in the nav drawer irrespective of whether the |
| // attendee is on-site or remote: |
| mNavDrawerItems.add(NAVDRAWER_ITEM_SOCIAL); |
| mNavDrawerItems.add(NAVDRAWER_ITEM_VIDEO_LIBRARY); |
| mNavDrawerItems.add(NAVDRAWER_ITEM_SEPARATOR_SPECIAL); |
| mNavDrawerItems.add(NAVDRAWER_ITEM_SETTINGS); |
| |
| createNavDrawerItems(); |
| } |
| |
| @Override |
| public void onBackPressed() { |
| if (isNavDrawerOpen()) { |
| closeNavDrawer(); |
| } else { |
| super.onBackPressed(); |
| } |
| } |
| |
| private void createNavDrawerItems() { |
| mDrawerItemsListContainer = (ViewGroup) findViewById(R.id.navdrawer_items_list); |
| if (mDrawerItemsListContainer == null) { |
| return; |
| } |
| |
| mNavDrawerItemViews = new View[mNavDrawerItems.size()]; |
| mDrawerItemsListContainer.removeAllViews(); |
| int i = 0; |
| for (int itemId : mNavDrawerItems) { |
| mNavDrawerItemViews[i] = makeNavDrawerItem(itemId, mDrawerItemsListContainer); |
| mDrawerItemsListContainer.addView(mNavDrawerItemViews[i]); |
| ++i; |
| } |
| } |
| |
| /** |
| * Sets up the given navdrawer item's appearance to the selected state. Note: this could |
| * also be accomplished (perhaps more cleanly) with state-based layouts. |
| */ |
| private void setSelectedNavDrawerItem(int itemId) { |
| if (mNavDrawerItemViews != null) { |
| for (int i = 0; i < mNavDrawerItemViews.length; i++) { |
| if (i < mNavDrawerItems.size()) { |
| int thisItemId = mNavDrawerItems.get(i); |
| formatNavDrawerItem(mNavDrawerItemViews[i], thisItemId, itemId == thisItemId); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { |
| if (key.equals(PrefUtils.PREF_ATTENDEE_AT_VENUE)) { |
| LOGD(TAG, "Attendee at venue preference changed, repopulating nav drawer and menu."); |
| populateNavDrawer(); |
| invalidateOptionsMenu(); |
| } |
| } |
| |
| @Override |
| protected void onPostCreate(Bundle savedInstanceState) { |
| super.onPostCreate(savedInstanceState); |
| setupNavDrawer(); |
| setupAccountBox(); |
| |
| trySetupSwipeRefresh(); |
| updateSwipeRefreshProgressBarTop(); |
| |
| View mainContent = findViewById(R.id.main_content); |
| if (mainContent != null) { |
| mainContent.setAlpha(0); |
| mainContent.animate().alpha(1).setDuration(MAIN_CONTENT_FADEIN_DURATION); |
| } else { |
| LOGW(TAG, "No view with ID main_content to fade in."); |
| } |
| } |
| |
| /** |
| * Sets up the account box. The account box is the area at the top of the nav drawer that |
| * shows which account the user is logged in as, and lets them switch accounts. It also |
| * shows the user's Google+ cover photo as background. |
| */ |
| private void setupAccountBox() { |
| mAccountListContainer = (LinearLayout) findViewById(R.id.account_list); |
| |
| if (mAccountListContainer == null) { |
| //This activity does not have an account box |
| return; |
| } |
| |
| final View chosenAccountView = findViewById(R.id.chosen_account_view); |
| Account chosenAccount = AccountUtils.getActiveAccount(this); |
| if (chosenAccount == null) { |
| // No account logged in; hide account box |
| chosenAccountView.setVisibility(View.GONE); |
| mAccountListContainer.setVisibility(View.GONE); |
| return; |
| } else { |
| chosenAccountView.setVisibility(View.VISIBLE); |
| mAccountListContainer.setVisibility(View.INVISIBLE); |
| } |
| |
| AccountManager am = AccountManager.get(this); |
| Account[] accountArray = am.getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); |
| List<Account> accounts = new ArrayList<Account>(Arrays.asList(accountArray)); |
| accounts.remove(chosenAccount); |
| |
| ImageView coverImageView = (ImageView) chosenAccountView.findViewById(R.id.profile_cover_image); |
| ImageView profileImageView = (ImageView) chosenAccountView.findViewById(R.id.profile_image); |
| TextView nameTextView = (TextView) chosenAccountView.findViewById(R.id.profile_name_text); |
| TextView email = (TextView) chosenAccountView.findViewById(R.id.profile_email_text); |
| mExpandAccountBoxIndicator = (ImageView) findViewById(R.id.expand_account_box_indicator); |
| |
| String name = AccountUtils.getPlusName(this); |
| if (name == null) { |
| nameTextView.setVisibility(View.GONE); |
| } else { |
| nameTextView.setVisibility(View.VISIBLE); |
| nameTextView.setText(name); |
| } |
| |
| String imageUrl = AccountUtils.getPlusImageUrl(this); |
| if (imageUrl != null) { |
| mImageLoader.loadImage(imageUrl, profileImageView); |
| } |
| |
| String coverImageUrl = AccountUtils.getPlusCoverUrl(this); |
| if (coverImageUrl != null) { |
| mImageLoader.loadImage(coverImageUrl, coverImageView); |
| } else { |
| coverImageView.setImageResource(R.drawable.default_cover); |
| } |
| |
| email.setText(chosenAccount.name); |
| |
| if (accounts.isEmpty()) { |
| // There's only one account on the device, so no need for a switcher. |
| mExpandAccountBoxIndicator.setVisibility(View.GONE); |
| mAccountListContainer.setVisibility(View.GONE); |
| chosenAccountView.setEnabled(false); |
| return; |
| } |
| |
| chosenAccountView.setEnabled(true); |
| |
| mExpandAccountBoxIndicator.setVisibility(View.VISIBLE); |
| chosenAccountView.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| mAccountBoxExpanded = !mAccountBoxExpanded; |
| setupAccountBoxToggle(); |
| } |
| }); |
| setupAccountBoxToggle(); |
| |
| populateAccountList(accounts); |
| } |
| |
| private void populateAccountList(List<Account> accounts) { |
| mAccountListContainer.removeAllViews(); |
| |
| LayoutInflater layoutInflater = LayoutInflater.from(this); |
| for (Account account : accounts) { |
| View itemView = layoutInflater.inflate(R.layout.list_item_account, |
| mAccountListContainer, false); |
| ((TextView) itemView.findViewById(R.id.profile_email_text)) |
| .setText(account.name); |
| final String accountName = account.name; |
| String imageUrl = AccountUtils.getPlusImageUrl(this, accountName); |
| if (!TextUtils.isEmpty(imageUrl)) { |
| mImageLoader.loadImage(imageUrl, |
| (ImageView) itemView.findViewById(R.id.profile_image)); |
| } |
| itemView.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| ConnectivityManager cm = (ConnectivityManager) |
| getSystemService(CONNECTIVITY_SERVICE); |
| NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); |
| if (activeNetwork == null || !activeNetwork.isConnected()) { |
| // if there's no network, don't try to change the selected account |
| Toast.makeText(BaseActivity.this, R.string.no_connection_cant_login, |
| Toast.LENGTH_SHORT).show(); |
| mDrawerLayout.closeDrawer(Gravity.START); |
| return; |
| } else { |
| LOGD(TAG, "User requested switch to account: " + accountName); |
| AccountUtils.setActiveAccount(BaseActivity.this, accountName); |
| onAccountChangeRequested(); |
| startLoginProcess(); |
| mAccountBoxExpanded = false; |
| setupAccountBoxToggle(); |
| mDrawerLayout.closeDrawer(Gravity.START); |
| setupAccountBox(); |
| } |
| } |
| }); |
| mAccountListContainer.addView(itemView); |
| } |
| } |
| |
| protected void onAccountChangeRequested() { |
| // override if you want to be notified when another account has been selected account has changed |
| } |
| |
| private void setupAccountBoxToggle() { |
| int selfItem = getSelfNavDrawerItem(); |
| if (mDrawerLayout == null || selfItem == NAVDRAWER_ITEM_INVALID) { |
| // this Activity does not have a nav drawer |
| return; |
| } |
| mExpandAccountBoxIndicator.setImageResource(mAccountBoxExpanded |
| ? R.drawable.ic_drawer_accounts_collapse |
| : R.drawable.ic_drawer_accounts_expand); |
| int hideTranslateY = -mAccountListContainer.getHeight() / 4; // last 25% of animation |
| if (mAccountBoxExpanded && mAccountListContainer.getTranslationY() == 0) { |
| // initial setup |
| mAccountListContainer.setAlpha(0); |
| mAccountListContainer.setTranslationY(hideTranslateY); |
| } |
| |
| AnimatorSet set = new AnimatorSet(); |
| set.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| mDrawerItemsListContainer.setVisibility(mAccountBoxExpanded |
| ? View.INVISIBLE : View.VISIBLE); |
| mAccountListContainer.setVisibility(mAccountBoxExpanded |
| ? View.VISIBLE : View.INVISIBLE); |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animation) { |
| onAnimationEnd(animation); |
| } |
| }); |
| |
| if (mAccountBoxExpanded) { |
| mAccountListContainer.setVisibility(View.VISIBLE); |
| AnimatorSet subSet = new AnimatorSet(); |
| subSet.playTogether( |
| ObjectAnimator.ofFloat(mAccountListContainer, View.ALPHA, 1) |
| .setDuration(ACCOUNT_BOX_EXPAND_ANIM_DURATION), |
| ObjectAnimator.ofFloat(mAccountListContainer, View.TRANSLATION_Y, 0) |
| .setDuration(ACCOUNT_BOX_EXPAND_ANIM_DURATION)); |
| set.playSequentially( |
| ObjectAnimator.ofFloat(mDrawerItemsListContainer, View.ALPHA, 0) |
| .setDuration(ACCOUNT_BOX_EXPAND_ANIM_DURATION), |
| subSet); |
| set.start(); |
| } else { |
| mDrawerItemsListContainer.setVisibility(View.VISIBLE); |
| AnimatorSet subSet = new AnimatorSet(); |
| subSet.playTogether( |
| ObjectAnimator.ofFloat(mAccountListContainer, View.ALPHA, 0) |
| .setDuration(ACCOUNT_BOX_EXPAND_ANIM_DURATION), |
| ObjectAnimator.ofFloat(mAccountListContainer, View.TRANSLATION_Y, |
| hideTranslateY) |
| .setDuration(ACCOUNT_BOX_EXPAND_ANIM_DURATION)); |
| set.playSequentially( |
| subSet, |
| ObjectAnimator.ofFloat(mDrawerItemsListContainer, View.ALPHA, 1) |
| .setDuration(ACCOUNT_BOX_EXPAND_ANIM_DURATION)); |
| set.start(); |
| } |
| |
| set.start(); |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| int id = item.getItemId(); |
| switch (id) { |
| case R.id.menu_about: |
| HelpUtils.showAbout(this); |
| return true; |
| |
| case R.id.menu_wifi: |
| WiFiUtils.showWiFiDialog(this); |
| return true; |
| |
| case R.id.menu_i_o_hunt: |
| launchIoHunt(); |
| return true; |
| |
| case R.id.menu_debug: |
| if (BuildConfig.DEBUG) { |
| startActivity(new Intent(this, DebugActionRunnerActivity.class)); |
| } |
| return true; |
| |
| case R.id.menu_refresh: |
| requestDataRefresh(); |
| break; |
| |
| case R.id.menu_io_extended: |
| startActivity(new Intent(Intent.ACTION_VIEW, |
| Uri.parse(Config.IO_EXTENDED_LINK))); |
| break; |
| |
| case R.id.menu_map: |
| startActivity(new Intent(this, UIUtils.getMapActivityClass(this))); |
| finish(); |
| break; |
| } |
| return super.onOptionsItemSelected(item); |
| } |
| |
| private void launchIoHunt() { |
| if (!TextUtils.isEmpty(Config.IO_HUNT_PACKAGE_NAME)) { |
| LOGD(TAG, "Attempting to launch I/O hunt."); |
| PackageManager pm = getPackageManager(); |
| Intent launchIntent = pm.getLaunchIntentForPackage(Config.IO_HUNT_PACKAGE_NAME); |
| if (launchIntent != null) { |
| // start I/O Hunt |
| LOGD(TAG, "I/O hunt intent found, launching."); |
| startActivity(launchIntent); |
| } else { |
| // send user to the Play Store to download it |
| LOGD(TAG, "I/O hunt intent NOT found, going to Play Store."); |
| Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse( |
| Config.PLAY_STORE_URL_PREFIX + Config.IO_HUNT_PACKAGE_NAME)); |
| startActivity(intent); |
| } |
| } |
| } |
| |
| protected void requestDataRefresh() { |
| Account activeAccount = AccountUtils.getActiveAccount(this); |
| ContentResolver contentResolver = getContentResolver(); |
| if (contentResolver.isSyncActive(activeAccount, ScheduleContract.CONTENT_AUTHORITY)) { |
| LOGD(TAG, "Ignoring manual sync request because a sync is already in progress."); |
| return; |
| } |
| mManualSyncRequest = true; |
| LOGD(TAG, "Requesting manual data refresh."); |
| SyncHelper.requestManualSync(activeAccount); |
| } |
| |
| private void goToNavDrawerItem(int item) { |
| Intent intent; |
| switch (item) { |
| case NAVDRAWER_ITEM_MY_SCHEDULE: |
| intent = new Intent(this, MyScheduleActivity.class); |
| startActivity(intent); |
| finish(); |
| break; |
| case NAVDRAWER_ITEM_EXPLORE: |
| intent = new Intent(this, BrowseSessionsActivity.class); |
| startActivity(intent); |
| finish(); |
| break; |
| case NAVDRAWER_ITEM_MAP: |
| intent = new Intent(this, UIUtils.getMapActivityClass(this)); |
| startActivity(intent); |
| finish(); |
| break; |
| case NAVDRAWER_ITEM_SOCIAL: |
| intent = new Intent(this, SocialActivity.class); |
| startActivity(intent); |
| finish(); |
| break; |
| case NAVDRAWER_ITEM_EXPERTS_DIRECTORY: |
| intent = new Intent(this, ExpertsDirectoryActivity.class); |
| startActivity(intent); |
| finish(); |
| break; |
| case NAVDRAWER_ITEM_PEOPLE_IVE_MET: |
| intent = new Intent(this, PeopleIveMetActivity.class); |
| startActivity(intent); |
| finish(); |
| break; |
| case NAVDRAWER_ITEM_SIGN_IN: |
| signInOrCreateAnAccount(); |
| break; |
| case NAVDRAWER_ITEM_SETTINGS: |
| intent = new Intent(this, SettingsActivity.class); |
| startActivity(intent); |
| break; |
| case NAVDRAWER_ITEM_VIDEO_LIBRARY: |
| intent = new Intent(this, VideoLibraryActivity.class); |
| startActivity(intent); |
| finish(); |
| break; |
| } |
| } |
| |
| private void signInOrCreateAnAccount() { |
| //Get list of accounts on device. |
| AccountManager am = AccountManager.get(BaseActivity.this); |
| Account[] accountArray = am.getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); |
| if (accountArray.length == 0) { |
| //Send the user to the "Add Account" page. |
| Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT); |
| intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, new String[] {"com.google"}); |
| startActivity(intent); |
| } else { |
| //Try to log the user in with the first account on the device. |
| startLoginProcess(); |
| mDrawerLayout.closeDrawer(Gravity.START); |
| } |
| } |
| |
| private void onNavDrawerItemClicked(final int itemId) { |
| if (itemId == getSelfNavDrawerItem()) { |
| mDrawerLayout.closeDrawer(Gravity.START); |
| return; |
| } |
| |
| if (isSpecialItem(itemId)) { |
| goToNavDrawerItem(itemId); |
| } else { |
| // launch the target Activity after a short delay, to allow the close animation to play |
| mHandler.postDelayed(new Runnable() { |
| @Override |
| public void run() { |
| goToNavDrawerItem(itemId); |
| } |
| }, NAVDRAWER_LAUNCH_DELAY); |
| |
| // change the active item on the list so the user can see the item changed |
| setSelectedNavDrawerItem(itemId); |
| // fade out the main content |
| View mainContent = findViewById(R.id.main_content); |
| if (mainContent != null) { |
| mainContent.animate().alpha(0).setDuration(MAIN_CONTENT_FADEOUT_DURATION); |
| } |
| } |
| |
| mDrawerLayout.closeDrawer(Gravity.START); |
| } |
| |
| protected void configureStandardMenuItems(Menu menu) { |
| MenuItem wifiItem = menu.findItem(R.id.menu_wifi); |
| if (wifiItem != null && !WiFiUtils.shouldOfferToSetupWifi(this, false)) { |
| wifiItem.setVisible(false); |
| } |
| |
| MenuItem debugItem = menu.findItem(R.id.menu_debug); |
| if (debugItem != null) { |
| debugItem.setVisible(BuildConfig.DEBUG); |
| } |
| |
| MenuItem ioExtendedItem = menu.findItem(R.id.menu_io_extended); |
| if (ioExtendedItem != null) { |
| ioExtendedItem.setVisible(PrefUtils.shouldOfferIOExtended(this, false)); |
| } |
| |
| // if attendee is remote, show map on the overflow instead of on the nav bar |
| final boolean isRemote = !PrefUtils.isAttendeeAtVenue(this); |
| final MenuItem mapItem = menu.findItem(R.id.menu_map); |
| if (mapItem != null) { |
| mapItem.setVisible(isRemote); |
| } |
| |
| MenuItem ioHuntItem = menu.findItem(R.id.menu_i_o_hunt); |
| if (ioHuntItem != null) { |
| ioHuntItem.setVisible(!isRemote && !TextUtils.isEmpty(Config.IO_HUNT_PACKAGE_NAME)); |
| } |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| |
| // Verifies the proper version of Google Play Services exists on the device. |
| PlayServicesUtils.checkGooglePlaySevices(this); |
| |
| // Watch for sync state changes |
| mSyncStatusObserver.onStatusChanged(0); |
| final int mask = ContentResolver.SYNC_OBSERVER_TYPE_PENDING | |
| ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE; |
| mSyncObserverHandle = ContentResolver.addStatusChangeListener(mask, mSyncStatusObserver); |
| } |
| |
| @Override |
| protected void onPause() { |
| super.onPause(); |
| if (mSyncObserverHandle != null) { |
| ContentResolver.removeStatusChangeListener(mSyncObserverHandle); |
| mSyncObserverHandle = null; |
| } |
| } |
| |
| /** |
| * Converts an intent into a {@link Bundle} suitable for use as fragment arguments. |
| */ |
| public static Bundle intentToFragmentArguments(Intent intent) { |
| Bundle arguments = new Bundle(); |
| if (intent == null) { |
| return arguments; |
| } |
| |
| final Uri data = intent.getData(); |
| if (data != null) { |
| arguments.putParcelable("_uri", data); |
| } |
| |
| final Bundle extras = intent.getExtras(); |
| if (extras != null) { |
| arguments.putAll(intent.getExtras()); |
| } |
| |
| return arguments; |
| } |
| |
| /** |
| * Converts a fragment arguments bundle into an intent. |
| */ |
| public static Intent fragmentArgumentsToIntent(Bundle arguments) { |
| Intent intent = new Intent(); |
| if (arguments == null) { |
| return intent; |
| } |
| |
| final Uri data = arguments.getParcelable("_uri"); |
| if (data != null) { |
| intent.setData(data); |
| } |
| |
| intent.putExtras(arguments); |
| intent.removeExtra("_uri"); |
| return intent; |
| } |
| |
| @Override |
| public void onStart() { |
| LOGD(TAG, "onStart"); |
| super.onStart(); |
| |
| // Perform one-time bootstrap setup, if needed |
| if (!PrefUtils.isDataBootstrapDone(this) && mDataBootstrapThread == null) { |
| LOGD(TAG, "One-time data bootstrap not done yet. Doing now."); |
| performDataBootstrap(); |
| } |
| |
| startLoginProcess(); |
| } |
| |
| /** |
| * Performs the one-time data bootstrap. This means taking our prepackaged conference data |
| * from the R.raw.bootstrap_data resource, and parsing it to populate the database. This |
| * data contains the sessions, speakers, etc. |
| */ |
| private void performDataBootstrap() { |
| final Context appContext = getApplicationContext(); |
| LOGD(TAG, "Starting data bootstrap background thread."); |
| mDataBootstrapThread = new Thread(new Runnable() { |
| @Override |
| public void run() { |
| LOGD(TAG, "Starting data bootstrap process."); |
| try { |
| // Load data from bootstrap raw resource |
| String bootstrapJson = JSONHandler.parseResource(appContext, R.raw.bootstrap_data); |
| |
| // Apply the data we read to the database with the help of the ConferenceDataHandler |
| ConferenceDataHandler dataHandler = new ConferenceDataHandler(appContext); |
| dataHandler.applyConferenceData(new String[]{bootstrapJson}, |
| Config.BOOTSTRAP_DATA_TIMESTAMP, false); |
| SyncHelper.performPostSyncChores(appContext); |
| LOGI(TAG, "End of bootstrap -- successful. Marking boostrap as done."); |
| PrefUtils.markSyncSucceededNow(appContext); |
| PrefUtils.markDataBootstrapDone(appContext); |
| getContentResolver().notifyChange(Uri.parse(ScheduleContract.CONTENT_AUTHORITY), |
| null, false); |
| } catch (IOException ex) { |
| // This is serious -- if this happens, the app won't work :-( |
| // This is unlikely to happen in production, but IF it does, we apply |
| // this workaround as a fallback: we pretend we managed to do the bootstrap |
| // and hope that a remote sync will work. |
| LOGE(TAG, "*** ERROR DURING BOOTSTRAP! Problem in bootstrap data?"); |
| LOGE(TAG, "Applying fallback -- marking boostrap as done; sync might fix problem."); |
| PrefUtils.markDataBootstrapDone(appContext); |
| } |
| |
| mDataBootstrapThread = null; |
| |
| // Request a manual sync immediately after the bootstrapping process, in case we |
| // have an active connection. Otherwise, the scheduled sync could take a while. |
| SyncHelper.requestManualSync(AccountUtils.getActiveAccount(appContext)); |
| } |
| }); |
| mDataBootstrapThread.start(); |
| } |
| |
| /** |
| * Returns the default account on the device. We use the rule that the first account |
| * should be the default. It's arbitrary, but the alternative would be showing an account |
| * chooser popup which wouldn't be a smooth first experience with the app. Since the user |
| * can easily switch the account with the nav drawer, we opted for this implementation. |
| */ |
| private String getDefaultAccount() { |
| // Choose first account on device. |
| LOGD(TAG, "Choosing default account (first account on device)"); |
| AccountManager am = AccountManager.get(this); |
| Account[] accounts = am.getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); |
| if (accounts.length == 0) { |
| // No Google accounts on device. |
| LOGW(TAG, "No Google accounts on device; not setting default account."); |
| return null; |
| } |
| |
| LOGD(TAG, "Default account is: " + accounts[0].name); |
| return accounts[0].name; |
| } |
| |
| |
| private void complainMustHaveGoogleAccount() { |
| LOGD(TAG, "Complaining about missing Google account."); |
| new AlertDialog.Builder(this) |
| .setTitle(R.string.google_account_required_title) |
| .setMessage(R.string.google_account_required_message) |
| .setPositiveButton(R.string.add_account, new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| promptAddAccount(); |
| } |
| }) |
| .setNegativeButton(R.string.not_now, new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| finish(); |
| } |
| }) |
| .show(); |
| } |
| |
| private void promptAddAccount() { |
| Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT); |
| intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, new String[]{"com.google"}); |
| startActivity(intent); |
| finish(); |
| } |
| |
| private void startLoginProcess() { |
| LOGD(TAG, "Starting login process."); |
| if (!AccountUtils.hasActiveAccount(this)) { |
| LOGD(TAG, "No active account, attempting to pick a default."); |
| String defaultAccount = getDefaultAccount(); |
| if (defaultAccount == null) { |
| LOGE(TAG, "Failed to pick default account (no accounts). Failing."); |
| complainMustHaveGoogleAccount(); |
| return; |
| } |
| LOGD(TAG, "Default to: " + defaultAccount); |
| AccountUtils.setActiveAccount(this, defaultAccount); |
| } |
| |
| if (!AccountUtils.hasActiveAccount(this)) { |
| LOGD(TAG, "Can't proceed with login -- no account chosen."); |
| return; |
| } else { |
| LOGD(TAG, "Chosen account: " + AccountUtils.getActiveAccountName(this)); |
| } |
| |
| String accountName = AccountUtils.getActiveAccountName(this); |
| LOGD(TAG, "Chosen account: " + AccountUtils.getActiveAccountName(this)); |
| |
| if (mLoginAndAuthHelper != null && mLoginAndAuthHelper.getAccountName().equals(accountName)) { |
| LOGD(TAG, "Helper already set up; simply starting it."); |
| mLoginAndAuthHelper.start(); |
| return; |
| } |
| |
| LOGD(TAG, "Starting login process with account " + accountName); |
| |
| if (mLoginAndAuthHelper != null) { |
| LOGD(TAG, "Tearing down old Helper, was " + mLoginAndAuthHelper.getAccountName()); |
| if (mLoginAndAuthHelper.isStarted()) { |
| LOGD(TAG, "Stopping old Helper"); |
| mLoginAndAuthHelper.stop(); |
| } |
| mLoginAndAuthHelper = null; |
| } |
| |
| LOGD(TAG, "Creating and starting new Helper with account: " + accountName); |
| mLoginAndAuthHelper = new LoginAndAuthHelper(this, this, accountName); |
| mLoginAndAuthHelper.start(); |
| } |
| |
| @Override |
| protected void onActivityResult(int requestCode, int resultCode, Intent data) { |
| if (mLoginAndAuthHelper == null || !mLoginAndAuthHelper.onActivityResult(requestCode, |
| resultCode, data)) { |
| super.onActivityResult(requestCode, resultCode, data); |
| } |
| } |
| |
| @Override |
| public void onStop() { |
| LOGD(TAG, "onStop"); |
| super.onStop(); |
| if (mLoginAndAuthHelper != null) { |
| mLoginAndAuthHelper.stop(); |
| } |
| } |
| |
| @Override |
| public void onPlusInfoLoaded(String accountName) { |
| setupAccountBox(); |
| populateNavDrawer(); |
| } |
| |
| /** |
| * Called when authentication succeeds. This may either happen because the user just |
| * authenticated for the first time (and went through the sign in flow), or because it's |
| * a returning user. |
| * @param accountName name of the account that just authenticated successfully. |
| * @param newlyAuthenticated If true, this user just authenticated for the first time. |
| * If false, it's a returning user. |
| */ |
| @Override |
| public void onAuthSuccess(String accountName, boolean newlyAuthenticated) { |
| Account account = new Account(accountName, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); |
| LOGD(TAG, "onAuthSuccess, account " + accountName + ", newlyAuthenticated=" + newlyAuthenticated); |
| |
| refreshAccountDependantData(); |
| |
| if (newlyAuthenticated) { |
| LOGD(TAG, "Enabling auto sync on content provider for account " + accountName); |
| SyncHelper.updateSyncInterval(this, account); |
| SyncHelper.requestManualSync(account); |
| } |
| |
| setupAccountBox(); |
| populateNavDrawer(); |
| registerGCMClient(); |
| } |
| |
| @Override |
| public void onAuthFailure(String accountName) { |
| LOGD(TAG, "Auth failed for account " + accountName); |
| refreshAccountDependantData(); |
| } |
| |
| protected void refreshAccountDependantData() { |
| // Force local data refresh for data that depends on the logged user: |
| LOGD(TAG, "Refreshing MySchedule data"); |
| getContentResolver().notifyChange(ScheduleContract.MySchedule.CONTENT_URI, null, false); |
| } |
| |
| protected void retryAuth() { |
| mLoginAndAuthHelper.retryAuthByUserRequest(); |
| } |
| |
| /** |
| * Initializes the Action Bar auto-hide (aka Quick Recall) effect. |
| */ |
| private void initActionBarAutoHide() { |
| mActionBarAutoHideEnabled = true; |
| mActionBarAutoHideMinY = getResources().getDimensionPixelSize( |
| R.dimen.action_bar_auto_hide_min_y); |
| mActionBarAutoHideSensivity = getResources().getDimensionPixelSize( |
| R.dimen.action_bar_auto_hide_sensivity); |
| } |
| |
| /** |
| * Indicates that the main content has scrolled (for the purposes of showing/hiding |
| * the action bar for the "action bar auto hide" effect). currentY and deltaY may be exact |
| * (if the underlying view supports it) or may be approximate indications: |
| * deltaY may be INT_MAX to mean "scrolled forward indeterminately" and INT_MIN to mean |
| * "scrolled backward indeterminately". currentY may be 0 to mean "somewhere close to the |
| * start of the list" and INT_MAX to mean "we don't know, but not at the start of the list" |
| */ |
| private void onMainContentScrolled(int currentY, int deltaY) { |
| if (deltaY > mActionBarAutoHideSensivity) { |
| deltaY = mActionBarAutoHideSensivity; |
| } else if (deltaY < -mActionBarAutoHideSensivity) { |
| deltaY = -mActionBarAutoHideSensivity; |
| } |
| |
| if (Math.signum(deltaY) * Math.signum(mActionBarAutoHideSignal) < 0) { |
| // deltaY is a motion opposite to the accumulated signal, so reset signal |
| mActionBarAutoHideSignal = deltaY; |
| } else { |
| // add to accumulated signal |
| mActionBarAutoHideSignal += deltaY; |
| } |
| |
| boolean shouldShow = currentY < mActionBarAutoHideMinY || |
| (mActionBarAutoHideSignal <= -mActionBarAutoHideSensivity); |
| autoShowOrHideActionBar(shouldShow); |
| } |
| |
| protected Toolbar getActionBarToolbar() { |
| if (mActionBarToolbar == null) { |
| mActionBarToolbar = (Toolbar) findViewById(R.id.toolbar_actionbar); |
| if (mActionBarToolbar != null) { |
| setSupportActionBar(mActionBarToolbar); |
| } |
| } |
| return mActionBarToolbar; |
| } |
| |
| protected void autoShowOrHideActionBar(boolean show) { |
| if (show == mActionBarShown) { |
| return; |
| } |
| |
| mActionBarShown = show; |
| onActionBarAutoShowOrHide(show); |
| } |
| |
| protected void enableActionBarAutoHide(final ListView listView) { |
| initActionBarAutoHide(); |
| listView.setOnScrollListener(new AbsListView.OnScrollListener() { |
| final static int ITEMS_THRESHOLD = 3; |
| int lastFvi = 0; |
| |
| @Override |
| public void onScrollStateChanged(AbsListView view, int scrollState) { |
| } |
| |
| @Override |
| public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { |
| onMainContentScrolled(firstVisibleItem <= ITEMS_THRESHOLD ? 0 : Integer.MAX_VALUE, |
| lastFvi - firstVisibleItem > 0 ? Integer.MIN_VALUE : |
| lastFvi == firstVisibleItem ? 0 : Integer.MAX_VALUE |
| ); |
| lastFvi = firstVisibleItem; |
| } |
| }); |
| } |
| |
| private View makeNavDrawerItem(final int itemId, ViewGroup container) { |
| boolean selected = getSelfNavDrawerItem() == itemId; |
| int layoutToInflate = 0; |
| if (itemId == NAVDRAWER_ITEM_SEPARATOR) { |
| layoutToInflate = R.layout.navdrawer_separator; |
| } else if (itemId == NAVDRAWER_ITEM_SEPARATOR_SPECIAL) { |
| layoutToInflate = R.layout.navdrawer_separator; |
| } else { |
| layoutToInflate = R.layout.navdrawer_item; |
| } |
| View view = getLayoutInflater().inflate(layoutToInflate, container, false); |
| |
| if (isSeparator(itemId)) { |
| // we are done |
| UIUtils.setAccessibilityIgnore(view); |
| return view; |
| } |
| |
| ImageView iconView = (ImageView) view.findViewById(R.id.icon); |
| TextView titleView = (TextView) view.findViewById(R.id.title); |
| int iconId = itemId >= 0 && itemId < NAVDRAWER_ICON_RES_ID.length ? |
| NAVDRAWER_ICON_RES_ID[itemId] : 0; |
| int titleId = itemId >= 0 && itemId < NAVDRAWER_TITLE_RES_ID.length ? |
| NAVDRAWER_TITLE_RES_ID[itemId] : 0; |
| |
| // set icon and text |
| iconView.setVisibility(iconId > 0 ? View.VISIBLE : View.GONE); |
| if (iconId > 0) { |
| iconView.setImageResource(iconId); |
| } |
| titleView.setText(getString(titleId)); |
| |
| formatNavDrawerItem(view, itemId, selected); |
| |
| view.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| onNavDrawerItemClicked(itemId); |
| } |
| }); |
| |
| return view; |
| } |
| |
| private boolean isSpecialItem(int itemId) { |
| return itemId == NAVDRAWER_ITEM_SETTINGS; |
| } |
| |
| private boolean isSeparator(int itemId) { |
| return itemId == NAVDRAWER_ITEM_SEPARATOR || itemId == NAVDRAWER_ITEM_SEPARATOR_SPECIAL; |
| } |
| |
| private void formatNavDrawerItem(View view, int itemId, boolean selected) { |
| if (isSeparator(itemId)) { |
| // not applicable |
| return; |
| } |
| |
| ImageView iconView = (ImageView) view.findViewById(R.id.icon); |
| TextView titleView = (TextView) view.findViewById(R.id.title); |
| |
| if (selected) { |
| view.setBackgroundResource(R.drawable.selected_navdrawer_item_background); |
| } |
| |
| // configure its appearance according to whether or not it's selected |
| titleView.setTextColor(selected ? |
| getResources().getColor(R.color.navdrawer_text_color_selected) : |
| getResources().getColor(R.color.navdrawer_text_color)); |
| iconView.setColorFilter(selected ? |
| getResources().getColor(R.color.navdrawer_icon_tint_selected) : |
| getResources().getColor(R.color.navdrawer_icon_tint)); |
| } |
| |
| /** Registers device on the GCM server, if necessary. */ |
| private void registerGCMClient() { |
| GCMRegistrar.checkDevice(this); |
| GCMRegistrar.checkManifest(this); |
| |
| final String regId = GCMRegistrar.getRegistrationId(this); |
| |
| if (TextUtils.isEmpty(regId)) { |
| // Automatically registers application on startup. |
| GCMRegistrar.register(this, Config.GCM_SENDER_ID); |
| |
| } else { |
| // Get the correct GCM key for the user. GCM key is a somewhat non-standard |
| // approach we use in this app. For more about this, check GCM.TXT. |
| final String gcmKey = AccountUtils.hasActiveAccount(this) ? |
| AccountUtils.getGcmKey(this, AccountUtils.getActiveAccountName(this)) : null; |
| // Device is already registered on GCM, needs to check if it is |
| // registered on our server as well. |
| if (ServerUtilities.isRegisteredOnServer(this, gcmKey)) { |
| // Skips registration. |
| LOGI(TAG, "Already registered on the GCM server with right GCM key."); |
| } else { |
| // Try to register again, but not in the UI thread. |
| // It's also necessary to cancel the thread onDestroy(), |
| // hence the use of AsyncTask instead of a raw thread. |
| mGCMRegisterTask = new AsyncTask<Void, Void, Void>() { |
| @Override |
| protected Void doInBackground(Void... params) { |
| LOGI(TAG, "Registering on the GCM server with GCM key: " |
| + AccountUtils.sanitizeGcmKey(gcmKey)); |
| boolean registered = ServerUtilities.register(BaseActivity.this, |
| regId, gcmKey); |
| // At this point all attempts to register with the app |
| // server failed, so we need to unregister the device |
| // from GCM - the app will try to register again when |
| // it is restarted. Note that GCM will send an |
| // unregistered callback upon completion, but |
| // GCMIntentService.onUnregistered() will ignore it. |
| if (!registered) { |
| LOGI(TAG, "GCM registration failed."); |
| GCMRegistrar.unregister(BaseActivity.this); |
| } else { |
| LOGI(TAG, "GCM registration successful."); |
| } |
| return null; |
| } |
| |
| @Override |
| protected void onPostExecute(Void result) { |
| mGCMRegisterTask = null; |
| } |
| }; |
| mGCMRegisterTask.execute(null, null, null); |
| } |
| } |
| } |
| |
| @Override |
| protected void onDestroy() { |
| super.onDestroy(); |
| |
| if (mGCMRegisterTask != null) { |
| LOGD(TAG, "Cancelling GCM registration task."); |
| mGCMRegisterTask.cancel(true); |
| } |
| |
| try { |
| GCMRegistrar.onDestroy(this); |
| } catch (Exception e) { |
| LOGW(TAG, "C2DM unregistration error", e); |
| } |
| |
| SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); |
| sp.unregisterOnSharedPreferenceChangeListener(this); |
| } |
| |
| private SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() { |
| @Override |
| public void onStatusChanged(int which) { |
| runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| String accountName = AccountUtils.getActiveAccountName(BaseActivity.this); |
| if (TextUtils.isEmpty(accountName)) { |
| onRefreshingStateChanged(false); |
| mManualSyncRequest = false; |
| return; |
| } |
| |
| Account account = new Account(accountName, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); |
| boolean syncActive = ContentResolver.isSyncActive( |
| account, ScheduleContract.CONTENT_AUTHORITY); |
| boolean syncPending = ContentResolver.isSyncPending( |
| account, ScheduleContract.CONTENT_AUTHORITY); |
| if (!syncActive && !syncPending) { |
| mManualSyncRequest = false; |
| } |
| onRefreshingStateChanged(syncActive || (mManualSyncRequest && syncPending)); |
| } |
| }); |
| } |
| }; |
| |
| protected void onRefreshingStateChanged(boolean refreshing) { |
| if (mSwipeRefreshLayout != null) { |
| mSwipeRefreshLayout.setRefreshing(refreshing); |
| } |
| } |
| |
| protected void enableDisableSwipeRefresh(boolean enable) { |
| if (mSwipeRefreshLayout != null) { |
| mSwipeRefreshLayout.setEnabled(enable); |
| } |
| } |
| |
| protected void registerHideableHeaderView(View hideableHeaderView) { |
| if (!mHideableHeaderViews.contains(hideableHeaderView)) { |
| mHideableHeaderViews.add(hideableHeaderView); |
| } |
| } |
| |
| protected void deregisterHideableHeaderView(View hideableHeaderView) { |
| if (mHideableHeaderViews.contains(hideableHeaderView)) { |
| mHideableHeaderViews.remove(hideableHeaderView); |
| } |
| } |
| |
| public LUtils getLUtils() { |
| return mLUtils; |
| } |
| |
| public int getThemedStatusBarColor() { |
| return mThemedStatusBarColor; |
| } |
| |
| public void setNormalStatusBarColor(int color) { |
| mNormalStatusBarColor = color; |
| if (mDrawerLayout != null) { |
| mDrawerLayout.setStatusBarBackgroundColor(mNormalStatusBarColor); |
| } |
| } |
| |
| protected void onActionBarAutoShowOrHide(boolean shown) { |
| if (mStatusBarColorAnimator != null) { |
| mStatusBarColorAnimator.cancel(); |
| } |
| mStatusBarColorAnimator = ObjectAnimator.ofInt( |
| (mDrawerLayout != null) ? mDrawerLayout : mLUtils, |
| (mDrawerLayout != null) ? "statusBarBackgroundColor" : "statusBarColor", |
| shown ? Color.BLACK : mNormalStatusBarColor, |
| shown ? mNormalStatusBarColor : Color.BLACK) |
| .setDuration(250); |
| if (mDrawerLayout != null) { |
| mStatusBarColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| @Override |
| public void onAnimationUpdate(ValueAnimator valueAnimator) { |
| ViewCompat.postInvalidateOnAnimation(mDrawerLayout); |
| } |
| }); |
| } |
| mStatusBarColorAnimator.setEvaluator(ARGB_EVALUATOR); |
| mStatusBarColorAnimator.start(); |
| |
| updateSwipeRefreshProgressBarTop(); |
| |
| for (View view : mHideableHeaderViews) { |
| if (shown) { |
| view.animate() |
| .translationY(0) |
| .alpha(1) |
| .setDuration(HEADER_HIDE_ANIM_DURATION) |
| .setInterpolator(new DecelerateInterpolator()); |
| } else { |
| view.animate() |
| .translationY(-view.getBottom()) |
| .alpha(0) |
| .setDuration(HEADER_HIDE_ANIM_DURATION) |
| .setInterpolator(new DecelerateInterpolator()); |
| } |
| } |
| } |
| |
| @Override |
| public boolean canSwipeRefreshChildScrollUp() { |
| return false; |
| } |
| } |