Java tutorial
/* * 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.razza.apps.iosched.myschedule; import com.razza.apps.iosched.Config; import com.razza.apps.iosched.R; import com.razza.apps.iosched.model.ScheduleHelper; import com.razza.apps.iosched.model.ScheduleItem; import com.razza.apps.iosched.provider.ScheduleContract; import com.razza.apps.iosched.session.SessionDetailActivity; import com.razza.apps.iosched.settings.SettingsUtils; import com.razza.apps.iosched.ui.BaseActivity; import com.razza.apps.iosched.util.AnalyticsHelper; import com.razza.apps.iosched.util.ThrottledContentObserver; import com.razza.apps.iosched.util.TimeUtils; import com.razza.apps.iosched.util.UIUtils; import com.razza.apps.iosched.util.LogUtils; import android.app.AlertDialog; import android.app.Fragment; import android.app.FragmentManager; import android.app.ListFragment; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.database.ContentObserver; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.design.widget.TabLayout; import android.support.v13.app.FragmentPagerAdapter; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; import android.text.SpannableString; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.util.Linkify; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.View; import android.widget.ScrollView; import android.widget.TextView; import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.Set; import static com.razza.apps.iosched.util.LogUtils.LOGD; import static com.razza.apps.iosched.util.LogUtils.LOGE; import static com.razza.apps.iosched.util.LogUtils.makeLogTag; public class MyScheduleActivity extends BaseActivity implements MyScheduleFragment.Listener { // Interval that a timer will redraw the UI when in conference day, so that time sensitive // widgets, like the "Now" and "Ended" indicators can be properly updated. private static final long INTERVAL_TO_REDRAW_UI = 60 * 1000L; private static final String SCREEN_LABEL = "My Schedule"; private static final String TAG = LogUtils.makeLogTag(MyScheduleActivity.class); // If true, we are in the wide (tablet) mode where we show conference days side by side; // if false, we are in narrow (handset) mode where we use a ViewPager and show only // one conference day at a time. private boolean mWideMode = false; // If in wide mode, we have MyScheduleView widgets showing each day private MyScheduleView[] mMyScheduleViewWide = new MyScheduleView[2]; // The adapters that serves as the source of data for the UI, indicating the available // items. We have one adapter per day of the conference. When we push new data into these // adapters, the corresponding UIs update automatically. private MyScheduleAdapter[] mScheduleAdapters = new MyScheduleAdapter[Config.CONFERENCE_DAYS.length]; // If non-null, the Activity will show day-0 tab (or column). private MyScheduleAdapter mDayZeroAdapter; // The ScheduleHelper is responsible for feeding data in a format suitable to the Adapter. private ScheduleHelper mDataHelper; // View pager and adapter (for narrow mode) ViewPager mViewPager = null; OurViewPagerAdapter mViewPagerAdapter = null; TabLayout mTabLayout = null; ScrollView mScrollViewWide; // Login failed butter bar View mButterBar; boolean mDestroyed = false; private static final String ARG_CONFERENCE_DAY_INDEX = "com.razza.apps.iosched.ARG_CONFERENCE_DAY_INDEX"; private Set<MyScheduleFragment> mMyScheduleFragments = new HashSet<MyScheduleFragment>(); public static final String EXTRA_DIALOG_TITLE = "com.razza.apps.iosched.EXTRA_DIALOG_TITLE"; public static final String EXTRA_DIALOG_MESSAGE = "com.razza.apps.iosched.EXTRA_DIALOG_MESSAGE"; public static final String EXTRA_DIALOG_YES = "com.razza.apps.iosched.EXTRA_DIALOG_YES"; public static final String EXTRA_DIALOG_NO = "com.razza.apps.iosched.EXTRA_DIALOG_NO"; public static final String EXTRA_DIALOG_URL = "com.razza.apps.iosched.EXTRA_DIALOG_URL"; private boolean mShowedAnnouncementDialog = false; private int baseTabViewId = 12345; private int mViewPagerScrollState = ViewPager.SCROLL_STATE_IDLE; public MyScheduleActivity() { mDataHelper = new ScheduleHelper(this); } @Override protected int getSelfNavDrawerItem() { return NAVDRAWER_ITEM_MY_SCHEDULE; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my_schedule); // Pre-process the intent received to open this activity to determine if it was a deep // link to a SessionDetail. Typically you wouldn't use this type of logic, but we need to // because of the path of session details page on the website is only /schedule and session // ids are part of the query parameters (sid). Intent intent = getIntent(); if (intent != null && !TextUtils.isEmpty(intent.getDataString())) { // Check format against website format. String intentDataString = intent.getDataString(); try { Uri dataUri = Uri.parse(intentDataString); String sessionId = dataUri.getQueryParameter("sid"); if (!TextUtils.isEmpty(sessionId)) { Uri data = ScheduleContract.Sessions.buildSessionUri(sessionId); Intent sessionDetailIntent = new Intent(MyScheduleActivity.this, SessionDetailActivity.class); sessionDetailIntent.setData(data); startActivity(sessionDetailIntent); finish(); } LogUtils.LOGD(TAG, "SessionId: " + sessionId); } catch (Exception exception) { LogUtils.LOGE(TAG, "Data uri existing but wasn't parsable for a session detail deep link"); } } // ANALYTICS SCREEN: View the My Schedule screen // Contains: Nothing (Page name is a constant) AnalyticsHelper.sendScreenView(SCREEN_LABEL); mViewPager = (ViewPager) findViewById(R.id.view_pager); mScrollViewWide = (ScrollView) findViewById(R.id.main_content_wide); mWideMode = findViewById(R.id.my_schedule_first_day) != null; if (SettingsUtils.isAttendeeAtVenue(this)) { mDayZeroAdapter = new MyScheduleAdapter(this, getLUtils()); prepareDayZeroAdapter(); } for (int i = 0; i < Config.CONFERENCE_DAYS.length; i++) { mScheduleAdapters[i] = new MyScheduleAdapter(this, getLUtils()); } mViewPagerAdapter = new OurViewPagerAdapter(getFragmentManager()); mViewPager.setAdapter(mViewPagerAdapter); if (mWideMode) { mMyScheduleViewWide[0] = (MyScheduleView) findViewById(R.id.my_schedule_first_day); mMyScheduleViewWide[0].setAdapter(mScheduleAdapters[0]); mMyScheduleViewWide[1] = (MyScheduleView) findViewById(R.id.my_schedule_second_day); mMyScheduleViewWide[1].setAdapter(mScheduleAdapters[1]); TextView firstDayHeaderView = (TextView) findViewById(R.id.day_label_first_day); TextView secondDayHeaderView = (TextView) findViewById(R.id.day_label_second_day); if (firstDayHeaderView != null) { firstDayHeaderView.setText(getDayName(0)); } if (secondDayHeaderView != null) { secondDayHeaderView.setText(getDayName(1)); } TextView zerothDayHeaderView = (TextView) findViewById(R.id.day_label_zeroth_day); MyScheduleView dayZeroView = (MyScheduleView) findViewById(R.id.my_schedule_zeroth_day); if (mDayZeroAdapter != null) { dayZeroView.setAdapter(mDayZeroAdapter); dayZeroView.setVisibility(View.VISIBLE); zerothDayHeaderView.setText(getDayName(-1)); zerothDayHeaderView.setVisibility(View.VISIBLE); } else { dayZeroView.setVisibility(View.GONE); zerothDayHeaderView.setVisibility(View.GONE); } } else { // it's PagerAdapter set. mTabLayout = (TabLayout) findViewById(R.id.sliding_tabs); mTabLayout.setTabsFromPagerAdapter(mViewPagerAdapter); mTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { mViewPager.setCurrentItem(tab.getPosition(), true); TextView view = (TextView) findViewById(baseTabViewId + tab.getPosition()); view.setContentDescription( getString(R.string.talkback_selected, getString(R.string.a11y_button, tab.getText()))); } @Override public void onTabUnselected(TabLayout.Tab tab) { TextView view = (TextView) findViewById(baseTabViewId + tab.getPosition()); view.setContentDescription(getString(R.string.a11y_button, tab.getText())); } @Override public void onTabReselected(TabLayout.Tab tab) { // Do nothing } }); mViewPager.setPageMargin(getResources().getDimensionPixelSize(R.dimen.my_schedule_page_margin)); mViewPager.setPageMarginDrawable(R.drawable.page_margin); setTabLayoutContentDescriptions(); } mButterBar = findViewById(R.id.butter_bar); removeLoginFailed(); overridePendingTransition(0, 0); addDataObservers(); } // This method is an ad-hoc implementation of Day 0. private void prepareDayZeroAdapter() { ScheduleItem item = new ScheduleItem(); item.title = "Room check-in and Registration"; // item.startTime = 1432742400000l; // 2015/05/27 9:00 AM PST item.startTime = 1464789600000l; // 2016/06/01 2:00 PM PST // item.endTime = 1432782000000l; // 2015/05/27 8:00 PM PST item.endTime = 1464811200000l; // 2015/06/01 8:00 PM PST item.type = ScheduleItem.BREAK; item.room = item.subtitle = "Conf. Rm. 1"; item.sessionType = ScheduleItem.SESSION_TYPE_MISC; mDayZeroAdapter.updateItems(Arrays.asList(item)); item = new ScheduleItem(); item.title = "Last Day of Learning Programs"; // item.startTime = 1432742400000l; // 2015/05/27 9:00 AM PST item.startTime = 1464768000000l; // 2016/06/01 8:00 AM PST // item.endTime = 1432782000000l; // 2015/05/27 8:00 PM PST item.endTime = 1464789600000l; // 2015/06/01 8:00 PM PST item.type = ScheduleItem.BREAK; item.room = item.subtitle = "Managing the L&D Function\n Facilitating L&D Methodologies"; item.sessionType = ScheduleItem.SESSION_TYPE_MISC; mDayZeroAdapter.updateItems(Arrays.asList(item)); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); if (mViewPager != null) { long now = UIUtils.getCurrentTime(this); selectDay(0); for (int i = 0; i < Config.CONFERENCE_DAYS.length; i++) { if (now >= Config.CONFERENCE_DAYS[i][0] && now <= Config.CONFERENCE_DAYS[i][1]) { selectDay(i); break; } } } setProgressBarTopWhenActionBarShown((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics())); } private void selectDay(int day) { int gap = mDayZeroAdapter != null ? 1 : 0; mViewPager.setCurrentItem(day + gap); setTimerToUpdateUI(day + gap); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); LogUtils.LOGD(TAG, "onNewIntent, extras " + intent.getExtras()); if (intent.hasExtra(EXTRA_DIALOG_MESSAGE)) { mShowedAnnouncementDialog = false; showAnnouncementDialogIfNeeded(intent); } } private String getDayName(int position) { long day1Start = Config.CONFERENCE_DAYS[0][0]; long day = 1000 * 60 * 60 * 24; return TimeUtils.formatShortDate(this, new Date(day1Start + day * position)); } private void setTabLayoutContentDescriptions() { LayoutInflater inflater = getLayoutInflater(); int gap = mDayZeroAdapter == null ? 0 : 1; for (int i = 0, count = mTabLayout.getTabCount(); i < count; i++) { TabLayout.Tab tab = mTabLayout.getTabAt(i); TextView view = (TextView) inflater.inflate(R.layout.tab_my_schedule, mTabLayout, false); view.setId(baseTabViewId + i); view.setText(tab.getText()); if (i == 0) { view.setContentDescription( getString(R.string.talkback_selected, getString(R.string.a11y_button, tab.getText()))); } else { view.setContentDescription(getString(R.string.a11y_button, tab.getText())); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { view.announceForAccessibility(getString(R.string.my_schedule_tab_desc_a11y, getDayName(i - gap))); } tab.setCustomView(view); } } private void removeLoginFailed() { mButterBar.setVisibility(View.GONE); deregisterHideableHeaderView(mButterBar); } @Override public void onAuthFailure(String accountName) { super.onAuthFailure(accountName); UIUtils.setUpButterBar(mButterBar, getString(R.string.login_failed_text), getString(R.string.login_failed_text_retry), new View.OnClickListener() { @Override public void onClick(View v) { removeLoginFailed(); retryAuth(); } }); registerHideableHeaderView(findViewById(R.id.butter_bar)); } @Override protected void onAccountChangeRequested() { super.onAccountChangeRequested(); removeLoginFailed(); } @Override public boolean canSwipeRefreshChildScrollUp() { if (mWideMode) { return ViewCompat.canScrollVertically(mScrollViewWide, -1); } // Prevent the swipe refresh by returning true here if (mViewPagerScrollState == ViewPager.SCROLL_STATE_DRAGGING) { return true; } for (MyScheduleFragment fragment : mMyScheduleFragments) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { if (!fragment.getUserVisibleHint()) { continue; } } return ViewCompat.canScrollVertically(fragment.getListView(), -1); } return false; } @Override public void onResume() { super.onResume(); updateData(); showAnnouncementDialogIfNeeded(getIntent()); } private void showAnnouncementDialogIfNeeded(Intent intent) { final String title = intent.getStringExtra(EXTRA_DIALOG_TITLE); final String message = intent.getStringExtra(EXTRA_DIALOG_MESSAGE); if (!mShowedAnnouncementDialog && !TextUtils.isEmpty(title) && !TextUtils.isEmpty(message)) { LogUtils.LOGD(TAG, "showAnnouncementDialogIfNeeded, title: " + title); LogUtils.LOGD(TAG, "showAnnouncementDialogIfNeeded, message: " + message); final String yes = intent.getStringExtra(EXTRA_DIALOG_YES); LogUtils.LOGD(TAG, "showAnnouncementDialogIfNeeded, yes: " + yes); final String no = intent.getStringExtra(EXTRA_DIALOG_NO); LogUtils.LOGD(TAG, "showAnnouncementDialogIfNeeded, no: " + no); final String url = intent.getStringExtra(EXTRA_DIALOG_URL); LogUtils.LOGD(TAG, "showAnnouncementDialogIfNeeded, url: " + url); final SpannableString spannable = new SpannableString(message == null ? "" : message); Linkify.addLinks(spannable, Linkify.WEB_URLS); AlertDialog.Builder builder = new AlertDialog.Builder(this); if (!TextUtils.isEmpty(title)) { builder.setTitle(title); } builder.setMessage(spannable); if (!TextUtils.isEmpty(no)) { builder.setNegativeButton(no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); } if (!TextUtils.isEmpty(yes)) { builder.setPositiveButton(yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(intent); } }); } final AlertDialog dialog = builder.create(); dialog.show(); final TextView messageView = (TextView) dialog.findViewById(android.R.id.message); if (messageView != null) { // makes the embedded links in the text clickable, if there are any messageView.setMovementMethod(LinkMovementMethod.getInstance()); } mShowedAnnouncementDialog = true; } } @Override protected void onDestroy() { super.onDestroy(); mDestroyed = true; removeDataObservers(); } protected void updateData() { for (int i = 0; i < Config.CONFERENCE_DAYS.length; i++) { mDataHelper.getScheduleDataAsync(mScheduleAdapters[i], Config.CONFERENCE_DAYS[i][0], Config.CONFERENCE_DAYS[i][1]); } } @Override public void onFragmentViewCreated(ListFragment fragment) { fragment.getListView() .addHeaderView(getLayoutInflater().inflate(R.layout.reserve_action_bar_space_header_view, null)); int dayIndex = fragment.getArguments().getInt(ARG_CONFERENCE_DAY_INDEX, 0); if (dayIndex < 0) { fragment.setListAdapter(mDayZeroAdapter); fragment.getListView().setRecyclerListener(mDayZeroAdapter); } else { fragment.setListAdapter(mScheduleAdapters[dayIndex]); fragment.getListView().setRecyclerListener(mScheduleAdapters[dayIndex]); } } @Override public void onFragmentAttached(MyScheduleFragment fragment) { mMyScheduleFragments.add(fragment); } @Override public void onFragmentDetached(MyScheduleFragment fragment) { mMyScheduleFragments.remove(fragment); } private class OurViewPagerAdapter extends FragmentPagerAdapter { public OurViewPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { LogUtils.LOGD(TAG, "Creating fragment #" + position); if (mDayZeroAdapter != null) { position--; } MyScheduleFragment frag = new MyScheduleFragment(); Bundle args = new Bundle(); args.putInt(ARG_CONFERENCE_DAY_INDEX, position); frag.setArguments(args); return frag; } @Override public int getCount() { return Config.CONFERENCE_DAYS.length + (mDayZeroAdapter == null ? 0 : 1); } @Override public CharSequence getPageTitle(int position) { return getDayName(position - (mDayZeroAdapter == null ? 0 : 1)); } } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.my_schedule, menu); return true; } protected void addDataObservers() { getContentResolver().registerContentObserver(ScheduleContract.BASE_CONTENT_URI, true, mObserver); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); sp.registerOnSharedPreferenceChangeListener(mPrefChangeListener); } public void removeDataObservers() { getContentResolver().unregisterContentObserver(mObserver); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); sp.unregisterOnSharedPreferenceChangeListener(mPrefChangeListener); } private final SharedPreferences.OnSharedPreferenceChangeListener mPrefChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sp, String key) { LogUtils.LOGD(TAG, "sharedpreferences key " + key + " changed, maybe reloading data."); for (MyScheduleAdapter adapter : mScheduleAdapters) { if (SettingsUtils.PREF_LOCAL_TIMES.equals(key)) { adapter.forceUpdate(); } else if (SettingsUtils.PREF_ATTENDEE_AT_VENUE.equals(key)) { updateData(); } } } }; private final ContentObserver mObserver = new ThrottledContentObserver( new ThrottledContentObserver.Callbacks() { @Override public void onThrottledContentObserverFired() { LogUtils.LOGD(TAG, "content may be changed, reloading data"); updateData(); } }); /** * If in conference day, redraw the day's UI every @{link #INTERVAL_TO_REDRAW_UI} ms, so * that time sensitive widgets, like "now", "ended" and appropriate styles are updated. * * @param today the index in the conference days array that corresponds to the current day. */ private void setTimerToUpdateUI(final int today) { new UpdateUIRunnable(this, today, new Handler()).scheduleNextRun(); } boolean hasBeenDestroyed() { return mDestroyed; } static final class UpdateUIRunnable implements Runnable { final WeakReference<MyScheduleActivity> weakRefToParent; final Handler handler; final int today; public UpdateUIRunnable(MyScheduleActivity activity, int today, Handler handler) { weakRefToParent = new WeakReference<MyScheduleActivity>(activity); this.handler = handler; this.today = today; } public void scheduleNextRun() { handler.postDelayed(this, INTERVAL_TO_REDRAW_UI); } @Override public void run() { MyScheduleActivity activity = weakRefToParent.get(); if (activity == null || activity.hasBeenDestroyed()) { LogUtils.LOGD(TAG, "Ativity is not valid anymore. Stopping UI Updater"); return; } LogUtils.LOGD(TAG, "Running MySchedule UI updater (now=" + new Date(UIUtils.getCurrentTime(activity)) + ")"); if (activity.mScheduleAdapters != null && activity.mScheduleAdapters.length > today && activity.mScheduleAdapters[today] != null) { try { activity.mScheduleAdapters[today].forceUpdate(); } finally { // schedule again this.scheduleNextRun(); } } } } }