/* * Copyright (c) 2011-2016 yvolk (Yuri Volkov), http://yurivolkov.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.andstatus.app.msg; import android.app.Activity; import android.app.SearchManager; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.provider.SearchRecentSuggestions; import android.support.annotation.NonNull; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.text.TextUtils; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.CheckBox; import android.widget.ListView; import android.widget.TextView; import org.andstatus.app.ActivityRequestCode; import org.andstatus.app.HelpActivity; import org.andstatus.app.IntentExtra; import org.andstatus.app.MyAction; import org.andstatus.app.R; import org.andstatus.app.SyncLoader; import org.andstatus.app.WhichPage; import org.andstatus.app.account.AccountSelector; import org.andstatus.app.account.MyAccount; import org.andstatus.app.context.MyContext; import org.andstatus.app.context.MyContextHolder; import org.andstatus.app.context.MyPreferences; import org.andstatus.app.context.MySettingsActivity; import org.andstatus.app.data.MatchedUri; import org.andstatus.app.data.TimelineSearchSuggestionsProvider; import org.andstatus.app.origin.Origin; import org.andstatus.app.service.CommandData; import org.andstatus.app.service.CommandEnum; import org.andstatus.app.service.MyServiceManager; import org.andstatus.app.service.MyServiceState; import org.andstatus.app.service.QueueViewer; import org.andstatus.app.test.SelectorActivityMock; import org.andstatus.app.timeline.Timeline; import org.andstatus.app.timeline.TimelineList; import org.andstatus.app.timeline.TimelineSelector; import org.andstatus.app.timeline.TimelineTitle; import org.andstatus.app.timeline.TimelineType; import org.andstatus.app.util.BundleUtils; import org.andstatus.app.util.MyCheckBox; import org.andstatus.app.util.MyLog; import org.andstatus.app.util.MyUrlSpan; import org.andstatus.app.util.SharedPreferencesUtil; import org.andstatus.app.util.TriState; import org.andstatus.app.util.UriUtils; import org.andstatus.app.util.ViewUtils; import org.andstatus.app.widget.MyBaseAdapter; import java.util.Collections; import java.util.Date; /** * @author yvolk@yurivolkov.com */ public class TimelineActivity extends MessageEditorListActivity implements MessageListContextMenuContainer, AbsListView.OnScrollListener { public static final String HORIZONTAL_ELLIPSIS = "\u2026"; /** Parameters for the next page request, not necessarily requested already */ private volatile TimelineListParameters paramsNew = null; /** Last parameters, requested to load. Thread safe. They are taken by a Loader at some time */ private volatile TimelineListParameters paramsToLoad; private volatile TimelineData listData; private MessageContextMenu contextMenu; private String mTextToShareViaThisApp = ""; private Uri mMediaToShareViaThisApp = Uri.EMPTY; private String mRateLimitText = ""; DrawerLayout mDrawerLayout; ActionBarDrawerToggle mDrawerToggle; protected volatile SelectorActivityMock selectorActivityMock; View syncOlderView = null; public static void startForTimeline(MyContext myContext, Activity activity, Timeline timeline, MyAccount newCurrentMyAccount, boolean clearTask) { if (newCurrentMyAccount != null && newCurrentMyAccount.isValid()) { myContext.persistentAccounts().setCurrentAccount(newCurrentMyAccount); } Intent intent = new Intent(myContext.context(), TimelineActivity.class); intent.setData(MatchedUri.getTimelineUri(timeline)); if (clearTask) { // On modifying activity back stack see http://stackoverflow.com/questions/11366700/modification-of-the-back-stack-in-android intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); } activity.startActivity(intent); } public static void goHome(Activity activity) { Intent intent = new Intent(activity, TimelineActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); activity.startActivity(intent); } @Override public void onRefresh() { syncWithInternet(getParamsLoaded().getTimeline(), true, true); } /** * This method is the first of the whole application to be called * when the application starts for the very first time. * So we may put some Application initialization code here. */ @Override protected void onCreate(Bundle savedInstanceState) { mLayoutId = R.layout.timeline; super.onCreate(savedInstanceState); showSyncIndicatorSetting = SharedPreferencesUtil.getBoolean( MyPreferences.KEY_SYNC_INDICATOR_ON_TIMELINE, true); if (HelpActivity.startFromActivity(this)) { return; } getParamsNew().setTimeline(myContext.persistentTimelines().getHome()); contextMenu = new MessageContextMenu(this); initializeDrawer(); getListView().setOnScrollListener(this); View view = findViewById(R.id.my_action_bar); if (view != null) { view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onTimelineTitleClick(v); } }); } syncOlderView = View.inflate(this, R.layout.sync_older, null); syncOlderView.findViewById(R.id.sync_older_button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { syncWithInternet(getParamsLoaded().getTimeline(), false, true); } }); if (savedInstanceState != null) { restoreActivityState(savedInstanceState); } else { parseNewIntent(getIntent()); } } @Override public TimelineAdapter getListAdapter() { return (TimelineAdapter) super.getListAdapter(); } @Override protected MyBaseAdapter newListAdapter() { return new TimelineAdapter(contextMenu, getListData()); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); mDrawerToggle.syncState(); } private void initializeDrawer() { mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerToggle = new ActionBarDrawerToggle( this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close ) { }; mDrawerLayout.addDrawerListener(mDrawerToggle); mDrawerToggle.setHomeAsUpIndicator(MyPreferences.getActionBarTextHomeIconResourceId()); } private void restoreActivityState(@NonNull Bundle savedInstanceState) { if (getParamsNew().restoreState(savedInstanceState)) { contextMenu.loadState(savedInstanceState); } getListData().collapseDuplicates(savedInstanceState.getBoolean( IntentExtra.COLLAPSE_DUPLICATES.key, MyPreferences.isCollapseDuplicates()), 0); if (MyLog.isVerboseEnabled()) { MyLog.v(this, "restoreActivityState; " + getParamsNew()); } } /** * View.OnClickListener */ public void onTimelineTitleClick(View item) { switch (MyPreferences.getTapOnATimelineTitleBehaviour()) { case SWITCH_TO_DEFAULT_TIMELINE: if (getParamsLoaded().isAtHome()) { onTimelineTypeButtonClick(item); } else { goHome(this); } break; case GO_TO_THE_TOP: onGoToTheTopButtonClick(item); break; case SELECT_TIMELINE: onTimelineTypeButtonClick(item); break; default: break; } } /** * View.OnClickListener */ public void onSwitchToDefaultTimelineButtonClick(View item) { closeDrawer(); goHome(this); } /** * View.OnClickListener */ public void onGoToTheTopButtonClick(View item) { closeDrawer(); if (getListData().mayHaveYoungerPage()) { showList(WhichPage.TOP); } else { TimelineListPositionStorage.setPosition(getListView(), 0); } } /** * View.OnClickListener */ public void onRefreshButtonClick(View item) { closeDrawer(); if (getListData().mayHaveYoungerPage() || getListView().getLastVisiblePosition() > TimelineListParameters.PAGE_SIZE / 2) { showList(WhichPage.CURRENT); } else { showList(WhichPage.YOUNGEST); } } /** * View.OnClickListener */ public void onCombinedTimelineToggleClick(View item) { closeDrawer(); switchView( getParamsLoaded().getTimeline().fromIsCombined(myContext, !getParamsLoaded().isTimelineCombined()), null); } private void closeDrawer() { ViewGroup mDrawerList = (ViewGroup) findViewById(R.id.navigation_drawer); mDrawerLayout.closeDrawer(mDrawerList); } public void onCollapseDuplicatesToggleClick(View view) { closeDrawer(); updateList(TriState.fromBoolean(((CheckBox) view).isChecked()), 0, false); } /** View.OnClickListener */ public void onTimelineTypeButtonClick(View item) { TimelineSelector.selectTimeline(this, ActivityRequestCode.SELECT_TIMELINE, getParamsNew().getTimeline(), getCurrentMyAccount()); closeDrawer(); } /** View.OnClickListener */ public void onSelectAccountButtonClick(View item) { if (myContext.persistentAccounts().size() > 1) { AccountSelector.selectAccount(TimelineActivity.this, ActivityRequestCode.SELECT_ACCOUNT, 0); } closeDrawer(); } /** * See <a href="http://developer.android.com/guide/topics/search/search-dialog.html">Creating * a Search Interface</a> */ @Override public boolean onSearchRequested() { onSearchRequested(false); return true; } private void onSearchRequested(boolean appGlobalSearch) { final String method = "onSearchRequested"; Bundle appSearchData = new Bundle(); appSearchData.putString(IntentExtra.TIMELINE_URI.key, MatchedUri.getTimelineUri( getParamsLoaded().getTimeline().fromSearch(myContext, appGlobalSearch)).toString()); appSearchData.putBoolean(IntentExtra.GLOBAL_SEARCH.key, appGlobalSearch); MyLog.v(this, method + ": " + appSearchData); startSearch(null, false, appSearchData, false); } @Override protected void onResume() { String method = "onResume"; if (!mFinishing) { if (getCurrentMyAccount().isValid()) { if (isConfigChanged()) { MyLog.v(this, method + "; Restarting this Activity to apply all new changes of configuration"); finish(); MyContextHolder.setExpiredIfConfigChanged(); switchView(getParamsLoaded().getTimeline(), null); } } else { MyLog.v(this, method + "; Finishing this Activity because there is no Account selected"); finish(); } } super.onResume(); } @Override protected void onPause() { final String method = "onPause"; if (MyLog.isVerboseEnabled()) { MyLog.v(this, method + "; instanceId=" + mInstanceId); } hideLoading(method); hideSyncing(method); crashTest(); saveListPosition(); myContext.persistentTimelines().saveChanged(); super.onPause(); } /** * Cancel notifications of loading timeline, which were set during Timeline downloading */ private void clearNotifications() { myContext.clearNotification(getParamsLoaded().getTimelineType()); MyServiceManager.sendForegroundCommand( CommandData.newAccountCommand(CommandEnum.CLEAR_NOTIFICATIONS, getParamsNew().getMyAccount())); } /** * May be executed on any thread * That advice doesn't fit here: * see http://stackoverflow.com/questions/5996885/how-to-wait-for-android-runonuithread-to-be-finished */ protected void saveListPosition() { if (getParamsLoaded().isLoaded() && isPositionRestored()) { Runnable runnable = new Runnable() { @Override public void run() { new TimelineListPositionStorage(getListAdapter(), getListView(), getParamsLoaded()).save(); } }; runOnUiThread(runnable); } } @Override public boolean onContextItemSelected(MenuItem item) { contextMenu.onContextItemSelected(item); return super.onContextItemSelected(item); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.create_message, menu); super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.timeline, menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { MyAccount ma = myContext.persistentAccounts().getCurrentAccount(); boolean enableSync = getParamsLoaded().getTimeline().isCombined() || ma.isValidAndSucceeded(); MenuItem item = menu.findItem(R.id.sync_menu_item); if (item != null) { item.setEnabled(enableSync); item.setVisible(enableSync); } prepareDrawer(); if (contextMenu != null) { contextMenu.setMyActor(MyAccount.EMPTY); } boolean enableGlobalSearch = myContext.persistentOrigins() .isGlobalSearchSupported(ma.getOrigin(), getParamsLoaded().getTimeline().isCombined()); item = menu.findItem(R.id.global_search_menu_id); if (item != null) { item.setEnabled(enableGlobalSearch); item.setVisible(enableGlobalSearch); } return super.onPrepareOptionsMenu(menu); } private void prepareDrawer() { View drawerView = findViewById(R.id.navigation_drawer); if (drawerView == null) { return; } TextView item = (TextView) drawerView.findViewById(R.id.timelineTypeButton); if (item != null) { item.setText(timelineTypeButtonText()); } prepareCombinedTimelineToggle(drawerView); updateAccountButtonText(drawerView); } private void prepareCombinedTimelineToggle(View drawerView) { if (ViewUtils.showView(drawerView, R.id.combinedTimelineToggle, // Show the "Combined" toggle even for one account to see messages, // which are not on the timeline. // E.g. messages by users, downloaded on demand. getParamsNew().getSelectedUserId() == 0 || getParamsNew().getSelectedUserId() == getParamsNew().getMyAccount().getUserId())) { MyCheckBox.setEnabled(drawerView, R.id.combinedTimelineToggle, getParamsLoaded().getTimeline().isCombined()); } } @Override public boolean onOptionsItemSelected(MenuItem item) { if (mDrawerToggle.onOptionsItemSelected(item)) { return true; } switch (item.getItemId()) { case android.R.id.home: if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) { mDrawerLayout.closeDrawer(Gravity.LEFT); } else { mDrawerLayout.openDrawer(Gravity.LEFT); } break; case R.id.global_search_menu_id: onSearchRequested(true); break; case R.id.search_menu_id: onSearchRequested(); break; case R.id.sync_menu_item: syncWithInternet(getParamsLoaded().getTimeline(), true, true); break; case R.id.commands_queue_id: startActivity(new Intent(getActivity(), QueueViewer.class)); break; case R.id.manage_timelines: startActivity(new Intent(getActivity(), TimelineList.class)); break; case R.id.preferences_menu_id: startMyPreferenceActivity(); break; case R.id.help_menu_id: onHelp(); break; default: break; } return super.onOptionsItemSelected(item); } private void onHelp() { Intent intent = new Intent(this, HelpActivity.class); intent.putExtra(HelpActivity.EXTRA_HELP_PAGE_INDEX, HelpActivity.PAGE_INDEX_USER_GUIDE); startActivity(intent); } public void onItemClick(TimelineViewItem item) { MyAccount ma = myContext.persistentAccounts().getAccountForThisMessage(item.getOriginId(), item.getMsgId(), item.getLinkedMyAccount(), getParamsNew().getMyAccount(), false); if (MyLog.isVerboseEnabled()) { MyLog.v(this, "onItemClick, " + item + "; " + item + " account=" + ma.getAccountName()); } if (item.getMsgId() <= 0) { return; } Uri uri = MatchedUri.getTimelineItemUri( Timeline.getTimeline(TimelineType.EVERYTHING, null, 0, myContext.persistentOrigins().fromId(item.getOriginId())), item.getMsgId()); String action = getIntent().getAction(); if (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)) { if (MyLog.isLoggable(this, MyLog.DEBUG)) { MyLog.d(this, "onItemClick, setData=" + uri); } setResult(RESULT_OK, new Intent().setData(uri)); } else { if (MyLog.isLoggable(this, MyLog.DEBUG)) { MyLog.d(this, "onItemClick, startActivity=" + uri); } startActivity(MyAction.VIEW_CONVERSATION.getIntent(uri)); } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // Empty } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { boolean up = false; if (firstVisibleItem == 0) { View v = getListView().getChildAt(0); int offset = (v == null) ? 0 : v.getTop(); up = offset == 0; if (up && getListData().mayHaveYoungerPage()) { showList(WhichPage.YOUNGER); } } // Idea from http://stackoverflow.com/questions/1080811/android-endless-list if ( !up && (visibleItemCount > 0) && (firstVisibleItem + visibleItemCount >= totalItemCount - 1) && getListData().mayHaveOlderPage()) { MyLog.d(this, "Start Loading older items, rows=" + totalItemCount); showList(WhichPage.OLDER); } } private String timelineTypeButtonText() { return TimelineTitle.load(myContext, getParamsLoaded().getTimeline(), getCurrentMyAccount()).title; } private void updateAccountButtonText(View drawerView) { TextView textView = (TextView) drawerView.findViewById(R.id.selectAccountButton); if (textView == null) { return; } String accountButtonText = getCurrentMyAccount().toAccountButtonText(myContext); textView.setText(accountButtonText); } @Override protected void onNewIntent(Intent intent) { if (mFinishing) { if (MyLog.isVerboseEnabled()) { MyLog.v(this, "onNewIntent, instanceId=" + mInstanceId + ", Is finishing"); } finish(); return; } if (!myContext.isReady()) { if (MyLog.isVerboseEnabled()) { MyLog.v(this, "onNewIntent, instanceId=" + mInstanceId + ", context is " + myContext.state()); } finish(); this.startActivity(intent); return; } if (MyLog.isVerboseEnabled()) { MyLog.v(this, "onNewIntent, instanceId=" + mInstanceId); } super.onNewIntent(intent); parseNewIntent(intent); if (isResumedMy() || getListData().size() > 0 || isLoading()) { showList(getParamsNew().whichPage); } } private void parseNewIntent(Intent intentNew) { if (MyLog.isVerboseEnabled()) { MyLog.v(this, "parseNewIntent:" + intentNew); } mRateLimitText = ""; getParamsNew().whichPage = WhichPage.load( intentNew.getStringExtra(IntentExtra.WHICH_PAGE.key), WhichPage.CURRENT); String searchQuery = intentNew.getStringExtra(SearchManager.QUERY); if (!parseAppSearchData(intentNew, searchQuery) && !getParamsNew().parseUri(intentNew.getData(), searchQuery)) { getParamsNew().setTimeline(myContext.persistentTimelines().getHome()); } setCurrentMyAccount(getParamsNew().getTimeline().getMyAccount(), getParamsNew().getTimeline().getOrigin()); if (Intent.ACTION_SEND.equals(intentNew.getAction())) { shareViaThisApplication(intentNew.getStringExtra(Intent.EXTRA_SUBJECT), intentNew.getStringExtra(Intent.EXTRA_TEXT), (Uri) intentNew.getParcelableExtra(Intent.EXTRA_STREAM)); } } private boolean parseAppSearchData(Intent intentNew, String searchQuery) { final String method = "parseAppSearchData"; Bundle appSearchData = intentNew.getBundleExtra(SearchManager.APP_DATA); if (appSearchData != null && getParamsNew().parseUri(Uri.parse(appSearchData.getString( IntentExtra.TIMELINE_URI.key, "")), searchQuery)) { if (getParamsNew().getTimeline().hasSearchQuery() && appSearchData.getBoolean(IntentExtra.GLOBAL_SEARCH.key, false)) { showSyncing(method, "Global search: " + getParamsNew().getTimeline().getSearchQuery()); for (Origin origin : myContext.persistentOrigins().originsForGlobalSearch( getParamsNew().getTimeline().getOrigin(), getParamsNew().getTimeline().isCombined())) { MyServiceManager.sendManualForegroundCommand( CommandData.newSearch(myContext, origin, getParamsNew().getTimeline().getSearchQuery())); } } return true; } return false; } private void shareViaThisApplication(String subject, String text, Uri mediaUri) { if (TextUtils.isEmpty(subject) && TextUtils.isEmpty(text) && UriUtils.isEmpty(mediaUri)) { return; } mTextToShareViaThisApp = ""; mMediaToShareViaThisApp = mediaUri; if (subjectHasAdditionalContent(subject, text)) { mTextToShareViaThisApp += subject; } if (!TextUtils.isEmpty(text)) { if (!TextUtils.isEmpty(mTextToShareViaThisApp)) { mTextToShareViaThisApp += " "; } mTextToShareViaThisApp += text; } MyLog.v(this, "Share via this app " + (!TextUtils.isEmpty(mTextToShareViaThisApp) ? "; text:'" + mTextToShareViaThisApp +"'" : "") + (!UriUtils.isEmpty(mMediaToShareViaThisApp) ? "; media:" + mMediaToShareViaThisApp.toString() : "")); AccountSelector.selectAccount(this, ActivityRequestCode.SELECT_ACCOUNT_TO_SHARE_VIA, 0); } static boolean subjectHasAdditionalContent(String subject, String text) { if (TextUtils.isEmpty(subject)) { return false; } if (TextUtils.isEmpty(text)) { return true; } return !text.startsWith(stripEllipsis(stripBeginning(subject))); } /** * Strips e.g. "Message - " or "Message:" */ static String stripBeginning(String textIn) { if (TextUtils.isEmpty(textIn)) { return ""; } int ind = textIn.indexOf("-"); if (ind < 0) { ind = textIn.indexOf(":"); } if (ind < 0) { return textIn; } String beginningSeparators = "-:;,.[] "; while ((ind < textIn.length()) && beginningSeparators.contains(String.valueOf(textIn.charAt(ind)))) { ind++; } if (ind >= textIn.length()) { return textIn; } return textIn.substring(ind); } static String stripEllipsis(String textIn) { if (TextUtils.isEmpty(textIn)) { return ""; } int ind = textIn.length() - 1; String ellipsis = "… ."; while (ind >= 0 && ellipsis.contains(String.valueOf(textIn.charAt(ind)))) { ind--; } if (ind < -1) { return ""; } return textIn.substring(0, ind+1); } private void updateScreen() { MyServiceManager.setServiceAvailable(); invalidateOptionsMenu(); getMessageEditor().updateScreen(); updateTitle(mRateLimitText); mDrawerToggle.setDrawerIndicatorEnabled(!getParamsLoaded().isAtHome()); ViewUtils.showView( findViewById(R.id.switchToDefaultTimelineButton), !getParamsLoaded().isAtHome()); MyCheckBox.setEnabled(this, R.id.collapseDuplicatesToggle, getListData().isCollapseDuplicates()); } @Override protected void updateTitle(String additionalTitleText) { TimelineTitle.load(myContext, getParamsLoaded().timeline, getCurrentMyAccount()). updateActivityTitle(this, additionalTitleText); } MessageContextMenu getContextMenu() { return contextMenu; } /** Parameters of currently shown Timeline */ @NonNull private TimelineListParameters getParamsLoaded() { return getListData().params; } @Override @NonNull public TimelineData getListData() { if (listData == null) { listData = new TimelineData(null, new TimelinePage( getParamsNew(), Collections.<TimelineViewItem>emptyList() )); } return listData; } private TimelineData setAndGetListData(TimelinePage pageLoaded) { TimelineData dataNew = new TimelineData(listData, pageLoaded); listData = dataNew; return dataNew; } @Override public void showList(WhichPage whichPage) { showList(whichPage, TriState.FALSE); } protected void showList(WhichPage whichPage, TriState chainedRequest) { showList(TimelineListParameters.clone(getReferenceParametersFor(whichPage), whichPage), chainedRequest); } @NonNull private TimelineListParameters getReferenceParametersFor(WhichPage whichPage) { switch (whichPage) { case OLDER: if (getListData().size() > 0) { return getListData().pages.get(getListData().pages.size()-1).params; } return getParamsLoaded(); case YOUNGER: if (getListData().size() > 0) { return getListData().pages.get(0).params; } return getParamsLoaded(); case EMPTY: return new TimelineListParameters(myContext); default: return getParamsNew(); } } /** * Prepare a query to the ContentProvider (to the database) and load the visible List of * messages with this data * This is done asynchronously. * This method should be called from UI thread only. */ protected void showList(TimelineListParameters params, TriState chainedRequest) { final String method = "showList"; if (params.isEmpty()) { MyLog.v(this, method + "; ignored empty request"); return; } boolean isDifferentRequest = !params.equals(paramsToLoad); paramsToLoad = params; if (isLoading() && chainedRequest != TriState.TRUE) { if(MyLog.isVerboseEnabled()) { if (isDifferentRequest) { MyLog.v(this, method + "; different while loading " + params.toSummary()); } else { MyLog.v(this, method + "; ignored duplicating " + params.toSummary()); } } } else { MyLog.v(this, method + (chainedRequest == TriState.TRUE ? "; chained" : "") + "; requesting " + (isDifferentRequest ? "" : "duplicating ") + params.toSummary()); saveListPosition(); showLoading(method, getText(R.string.loading) + " " + paramsToLoad.toSummary() + HORIZONTAL_ELLIPSIS); super.showList(chainedRequest.toBundle(paramsToLoad.whichPage.toBundle(), IntentExtra.CHAINED_REQUEST.key)); } } @Override protected SyncLoader newSyncLoader(Bundle args) { final String method = "newSyncLoader"; WhichPage whichPage = WhichPage.load(args); TimelineListParameters params = paramsToLoad == null || whichPage == WhichPage.EMPTY ? new TimelineListParameters(myContext) : paramsToLoad; if (params.whichPage != WhichPage.EMPTY) { MyLog.v(this, method + ": " + params); Intent intent = getIntent(); if (!params.getContentUri().equals(intent.getData())) { intent.setData(params.getContentUri()); } saveSearchQuery(); } return new TimelineLoader(params, BundleUtils.fromBundle(args, IntentExtra.INSTANCE_ID)); } private void saveSearchQuery() { if (getParamsNew().getTimeline().hasSearchQuery()) { // Record the query string in the recent queries // of the Suggestion Provider SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, TimelineSearchSuggestionsProvider.AUTHORITY, TimelineSearchSuggestionsProvider.MODE); suggestions.saveRecentQuery(getParamsNew().getTimeline().getSearchQuery(), null); } } @Override public void onLoadFinished(boolean keepCurrentPosition_in) { final String method = "onLoadFinished"; verboseListPositionLog(method, "started"); TimelineData dataLoaded = setAndGetListData(((TimelineLoader) getLoaded()).getPage()); MyLog.v(this, method + "; " + dataLoaded.params.toSummary()); // TODO start: Move this inside superclass boolean keepCurrentPosition = keepCurrentPosition_in && getListData().isSameTimeline && isPositionRestored() && dataLoaded.params.whichPage != WhichPage.TOP; super.onLoadFinished(keepCurrentPosition); if (dataLoaded.params.whichPage == WhichPage.TOP) { TimelineListPositionStorage.setPosition(getListView(), 0); getListAdapter().setPositionRestored(true); } // TODO end: Move this inside superclass if (!isPositionRestored()) { new TimelineListPositionStorage(getListAdapter(), getListView(), dataLoaded.params) .restore(); } TimelineListParameters otherParams = paramsToLoad; boolean isParamsChanged = otherParams != null && !dataLoaded.params.equals(otherParams); WhichPage otherPageToRequest = WhichPage.EMPTY; if (!isParamsChanged && getListData().size() < 10) { if (getListData().mayHaveYoungerPage()) { otherPageToRequest = WhichPage.YOUNGER; } else if (getListData().mayHaveOlderPage()) { otherPageToRequest = WhichPage.OLDER; } else if (!dataLoaded.params.whichPage.isYoungest()) { otherPageToRequest = WhichPage.YOUNGEST; } else if (dataLoaded.params.rowsLoaded == 0) { launchSyncIfNeeded(dataLoaded.params.timelineToSync); } } hideLoading(method); updateScreen(); clearNotifications(); if (isParamsChanged) { MyLog.v(this, method + "; Parameters changed, requesting " + otherParams.toSummary()); showList(otherParams, TriState.TRUE); } else if (otherPageToRequest != WhichPage.EMPTY) { MyLog.v(this, method + "; Nothing loaded, requesting " + otherPageToRequest); showList(otherPageToRequest, TriState.TRUE); } else { showSyncOlder(); } } private void showSyncOlder() { final ListView listView = getListView(); if (listView == null) { return; } if (getListData().mayHaveOlderPage() || (!getParamsLoaded().getTimeline().isCombined() && !myContext.persistentAccounts().getCurrentAccount().isValidAndSucceeded()) ) { listView.removeFooterView(syncOlderView); } else { listView.addFooterView(syncOlderView); MyUrlSpan.showText(syncOlderView, R.id.sync_older_button, String.format(getText(getParamsLoaded().getTimeline().isCombined() || getParamsLoaded().getTimeline().getOldestItemDate() == 0 ? R.string.options_menu_sync : R.string.sync_older_messages).toString(), new Date(getParamsLoaded().getTimeline().getOldestItemDate()).toString()), false, false); } } private void launchSyncIfNeeded(Timeline timelineToSync) { if (!timelineToSync.isEmpty()) { if (timelineToSync.getTimelineType() == TimelineType.EVERYTHING) { syncWithInternet(getParamsLoaded().getTimeline(), true, false); // Sync this one timeline and then - all syncable for this account if (getParamsNew().getMyAccount().isValidAndSucceeded()) { getParamsNew().getMyAccount().requestSync(); } } else { syncWithInternet(timelineToSync, true, false); } } } protected void syncWithInternet(Timeline timelineToSync, boolean syncYounger, boolean manuallyLaunched) { if (timelineToSync.isSyncableForOrigins()) { syncForAllOrigins(timelineToSync, syncYounger, manuallyLaunched); } else if (timelineToSync.isSyncableForAccounts()) { syncForAllAccounts(timelineToSync, syncYounger, manuallyLaunched); } else if (timelineToSync.isSyncable()) { syncOneTimeline(timelineToSync, syncYounger, manuallyLaunched); } else { hideSyncing("SyncWithInternet"); } } private void syncForAllOrigins(Timeline timelineToSync, boolean syncYounger, boolean manuallyLaunched) { for (Origin origin : myContext.persistentOrigins().originsToSync( timelineToSync.getMyAccount().getOrigin(), true, timelineToSync.hasSearchQuery())) { syncOneTimeline(timelineToSync.cloneForOrigin(myContext, origin), syncYounger, manuallyLaunched); } } private void syncForAllAccounts(Timeline timelineToSync, boolean syncYounger, boolean manuallyLaunched) { for (MyAccount ma : myContext.persistentAccounts().accountsToSync(timelineToSync.getMyAccount(), true)) { if (timelineToSync.getTimelineType() == TimelineType.EVERYTHING) { ma.requestSync(); } else { syncOneTimeline(timelineToSync.cloneForAccount(myContext, ma), syncYounger, manuallyLaunched); } } } private void syncOneTimeline(Timeline timeline, boolean syncYounger, boolean manuallyLaunched) { final String method = "syncOneTimeline"; if (timeline.isSyncable()) { setCircularSyncIndicator(method, true); showSyncing(method, getText(R.string.options_menu_sync)); MyServiceManager.sendForegroundCommand( CommandData.newTimelineCommand(syncYounger ? CommandEnum.GET_TIMELINE : CommandEnum.GET_OLDER_TIMELINE, timeline) .setManuallyLaunched(manuallyLaunched) ); } } protected void startMyPreferenceActivity() { finish(); startActivity(new Intent(this, MySettingsActivity.class)); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); getParamsNew().saveState(outState); outState.putBoolean(IntentExtra.COLLAPSE_DUPLICATES.key, getListData().isCollapseDuplicates()); contextMenu.saveState(outState); } protected void crashTest() { final String CRASH_TEST_STRING = "Crash test 2015-04-10"; if (MyLog.isVerboseEnabled() && getMessageEditor() != null && getMessageEditor().getData().body.contains(CRASH_TEST_STRING)) { MyLog.e(this, "Initiating crash test exception"); throw new NullPointerException("This is a test crash event"); } } @Override public void startActivityForResult(Intent intent, int requestCode) { if (selectorActivityMock != null) { selectorActivityMock.startActivityForResult(intent, requestCode); } else { super.startActivityForResult(intent, requestCode); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { MyLog.v(this, "onActivityResult; request:" + requestCode + ", result:" + (resultCode == RESULT_OK ? "ok" : "fail")); if (resultCode != RESULT_OK || data == null) { return; } switch (ActivityRequestCode.fromId(requestCode)) { case SELECT_ACCOUNT: accountSelected(data); break; case SELECT_ACCOUNT_TO_ACT_AS: accountToActAsSelected(data); break; case SELECT_ACCOUNT_TO_SHARE_VIA: accountToShareViaSelected(data); break; case SELECT_TIMELINE: Timeline timeline = myContext.persistentTimelines() .fromId(data.getLongExtra(IntentExtra.TIMELINE_ID.key, 0)); if (timeline.isValid()) { switchView(timeline, null); } break; default: super.onActivityResult(requestCode, resultCode, data); break; } } private void accountSelected(Intent data) { MyAccount ma = myContext.persistentAccounts().fromAccountName(data.getStringExtra(IntentExtra.ACCOUNT_NAME.key)); if (ma.isValid()) { switchView(getParamsLoaded().getTimeline().isCombined() ? getParamsLoaded().getTimeline() : getParamsLoaded().getTimeline().fromMyAccount(myContext, ma), ma); } } private void accountToActAsSelected(Intent data) { MyAccount ma = myContext.persistentAccounts().fromAccountName( data.getStringExtra(IntentExtra.ACCOUNT_NAME.key)); if (ma.isValid()) { contextMenu.setMyActor(ma); contextMenu.showContextMenu(); } } private void accountToShareViaSelected(Intent data) { MyAccount ma = myContext.persistentAccounts().fromAccountName(data.getStringExtra(IntentExtra.ACCOUNT_NAME.key)); getMessageEditor().startEditingSharedData(ma, mTextToShareViaThisApp, mMediaToShareViaThisApp); } @Override protected boolean isEditorVisible() { return getMessageEditor().isVisible(); } @Override protected boolean isCommandToShowInSyncIndicator(CommandData commandData) { switch (commandData.getCommand()) { case GET_TIMELINE: case GET_OLDER_TIMELINE: case FETCH_ATTACHMENT: case FETCH_AVATAR: case UPDATE_STATUS: case DESTROY_STATUS: case CREATE_FAVORITE: case DESTROY_FAVORITE: case FOLLOW_USER: case STOP_FOLLOWING_USER: case REBLOG: case DESTROY_REBLOG: return true; default: return false; } } @Override public boolean canSwipeRefreshChildScrollUp() { if (getListData().mayHaveYoungerPage()) { return true; } return super.canSwipeRefreshChildScrollUp(); } @Override protected void onReceiveAfterExecutingCommand(CommandData commandData) { switch (commandData.getCommand()) { case RATE_LIMIT_STATUS: if (commandData.getResult().getHourlyLimit() > 0) { mRateLimitText = commandData.getResult().getRemainingHits() + "/" + commandData.getResult().getHourlyLimit(); updateTitle(mRateLimitText); } break; default: break; } if (!TextUtils.isEmpty(syncingText)) { if (MyServiceManager.getServiceState() != MyServiceState.RUNNING) { hideSyncing("Service is not running"); } else if (isCommandToShowInSyncIndicator(commandData)) { showSyncing("After executing " + commandData.getCommand(), HORIZONTAL_ELLIPSIS); } } super.onReceiveAfterExecutingCommand(commandData); } @Override public boolean isRefreshNeededAfterExecuting(CommandData commandData) { boolean needed = super.isRefreshNeededAfterExecuting(commandData); switch (commandData.getCommand()) { case GET_TIMELINE: case GET_OLDER_TIMELINE: if (!getParamsLoaded().isLoaded() || getParamsLoaded().getTimelineType() != commandData.getTimelineType()) { break; } if (commandData.getResult().getDownloadedCount() > 0) { needed = true; } break; default: break; } return needed; } @Override protected boolean isAutoRefreshNow(boolean onStop) { return super.isAutoRefreshNow(onStop) && MyPreferences.isRefreshTimelineAutomatically(); } @Override public void onMessageEditorVisibilityChange() { hideSyncing("onMessageEditorVisibilityChange"); super.onMessageEditorVisibilityChange(); } @Override public Timeline getTimeline() { return getParamsLoaded().getTimeline(); } public void switchView(Timeline timeline, MyAccount newCurrentMyAccount) { MyAccount currentMyAccountToSet = MyAccount.EMPTY; if (newCurrentMyAccount != null && newCurrentMyAccount.isValid()) { currentMyAccountToSet = newCurrentMyAccount; } else if (timeline.getMyAccount().isValid()) { currentMyAccountToSet = timeline.getMyAccount(); } if (currentMyAccountToSet.isValid()) { setCurrentMyAccount(currentMyAccountToSet, currentMyAccountToSet.getOrigin()); myContext.persistentAccounts().setCurrentAccount(currentMyAccountToSet); } if (isFinishing() || !timeline.equals(getParamsLoaded().getTimeline())) { if (MyLog.isVerboseEnabled()) { MyLog.v(this, "switchTimelineActivity; " + timeline); } TimelineActivity.startForTimeline(myContext, this, timeline, currentMyAccountToSet, false); } else { showList(WhichPage.CURRENT); } } @NonNull public TimelineListParameters getParamsNew() { if (paramsNew == null) { paramsNew = new TimelineListParameters(myContext); } return paramsNew; } }