/*
* Copyright (C) 2008 Torgny Bjers
* Copyright (c) 2011 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 com.xorcode.andtweet;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Configuration;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AbsListView.OnScrollListener;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import com.xorcode.andtweet.data.AndTweetDatabase;
import com.xorcode.andtweet.data.AndTweetPreferences;
import com.xorcode.andtweet.data.AndTweetDatabase.Tweets;
import com.xorcode.andtweet.AndTweetService.CommandData;
import com.xorcode.andtweet.AndTweetService.CommandEnum;
import com.xorcode.andtweet.TwitterUser.CredentialsVerified;
/**
* @author torgny.bjers
*/
public class TimelineActivity extends ListActivity implements ITimelineActivity {
private static final String TAG = TimelineActivity.class.getSimpleName();
// Handler message codes
public static final int MSG_TWEETS_CHANGED = 1;
public static final int MSG_DATA_LOADING = 2;
/**
* My tweet ("What's happening?"...) is being sending
*/
public static final int MSG_UPDATE_STATUS = 3;
public static final int MSG_MANUAL_RELOAD = 4;
public static final int MSG_AUTHENTICATION_ERROR = 5;
public static final int MSG_LOAD_ITEMS = 6;
public static final int MSG_DIRECT_MESSAGES_CHANGED = 7;
public static final int MSG_SERVICE_UNAVAILABLE_ERROR = 8;
public static final int MSG_REPLIES_CHANGED = 9;
public static final int MSG_UPDATED_TITLE = 10;
public static final int MSG_CONNECTION_TIMEOUT_EXCEPTION = 11;
public static final int MSG_STATUS_DESTROY = 12;
public static final int MSG_FAVORITE_CREATE = 13;
public static final int MSG_FAVORITE_DESTROY = 14;
public static final int MSG_CONNECTION_EXCEPTION = 15;
// Handler message status codes
public static final int STATUS_LOAD_ITEMS_FAILURE = 0;
public static final int STATUS_LOAD_ITEMS_SUCCESS = 1;
// Dialog identifier codes
public static final int DIALOG_AUTHENTICATION_FAILED = 1;
public static final int DIALOG_SERVICE_UNAVAILABLE = 3;
public static final int DIALOG_EXTERNAL_STORAGE = 4;
public static final int DIALOG_EXTERNAL_STORAGE_MISSING = 6;
public static final int DIALOG_CONNECTION_TIMEOUT = 7;
public static final int DIALOG_EXECUTING_COMMAND = 8;
// Intent bundle result keys
public static final String INTENT_RESULT_KEY_AUTHENTICATION = "authentication";
// Bundle identifier keys
public static final String BUNDLE_KEY_CURRENT_PAGE = "currentPage";
public static final String BUNDLE_KEY_IS_LOADING = "isLoading";
/**
* Key prefix for the stored list position
*/
protected static final String LAST_POS_KEY = "last_position";
public static final int MILLISECONDS = 1000;
/**
* List footer for loading messages, appears at the bottom of the list of
* tweets In fact, it is not visible but it is used to find out when User
* wants to see items that were not loaded into the list...
*/
protected LinearLayout mListFooter;
protected Cursor mCursor;
protected NotificationManager mNM;
protected ProgressDialog mProgressDialog;
protected Handler mHandler;
/**
* Tweets are being loaded into the list starting from one page. More Tweets
* are being loaded in a case User scrolls down to the end of list.
*/
protected final static int PAGE_SIZE = 20;
/**
* Is saved position restored (or some default positions set)?
*/
protected boolean positionRestored = false;
/**
* Number of items (Tweets) in the list. It is used to find out when we need
* to load more items.
*/
protected int mTotalItemCount = 0;
/**
* Is connected to the application service?
*/
protected boolean mIsBound;
/**
* See {@link #mServiceCallback} also
*/
protected IAndTweetService mService;
/**
* Items are being loaded into the list (asynchronously...)
*/
protected boolean mIsLoading;
/**
* We are going to finish/restart onResume this Activity
*/
protected boolean mIsFinishingOnResume = false;
/**
* TODO: enum from
* com.xorcode.andtweet.data.AndTweetDatabase.Tweets.TIMELINE_TYPE_...
*/
protected int mTimelineType = Tweets.TIMELINE_TYPE_NONE;
/**
* True if this timeline is filtered using query string ("Mentions" are not
* counted here because they have separate TimelineType)
*/
protected boolean mSearchMode = false;
/**
* The string is not empty if this timeline is filtered using query string
* ("Mentions" are not counted here because they have separate TimelineType)
*/
protected String mQueryString = "";
/**
* Time when shared preferences where changed
*/
protected long preferencesChangeTime = 0;
/**
* 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) {
super.onCreate(savedInstanceState);
AndTweetPreferences.initialize(this, this);
preferencesChangeTime = AndTweetPreferences.getDefaultSharedPreferences().getLong(PreferencesActivity.KEY_PREFERENCES_CHANGE_TIME, 0);
if (Log.isLoggable(AndTweetService.APPTAG, Log.DEBUG)) {
AndTweetService.d(TAG, "onCreate, preferencesChangeTime=" + preferencesChangeTime);
}
if (!AndTweetPreferences.getSharedPreferences(PreferenceManager.KEY_HAS_SET_DEFAULT_VALUES, MODE_PRIVATE).getBoolean(PreferenceManager.KEY_HAS_SET_DEFAULT_VALUES, false)) {
Log.i(TAG, "We are running the Application for the very first time?");
startActivity(new Intent(this, SplashActivity.class));
finish();
}
if (TwitterUser.getTwitterUser().getCredentialsVerified() == CredentialsVerified.NEVER) {
Log.i(TAG, "TwitterUser '" + TwitterUser.getTwitterUser().getUsername() + "' was not ever verified?");
startActivity(new Intent(this, SplashActivity.class));
finish();
}
setTimelineType(getIntent());
// Request window features before loading the content view
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
loadTheme();
setContentView(R.layout.tweetlist);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.timeline_title);
/*
* if (mSP.getBoolean("storage_use_external", false)) { if
* (!Environment.
* getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
* showDialog(DIALOG_EXTERNAL_STORAGE_MISSING); } if
* (Environment.getExternalStorageState
* ().equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
* Toast.makeText(this,
* "External storage mounted read-only. Cannot write to database. Please re-mount your storage and try again."
* , Toast.LENGTH_LONG).show(); destroyService(); finish(); } } if
* (Environment
* .getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { if
* (!mSP.getBoolean("confirmed_external_storage_use", false)) {
* showDialog(DIALOG_EXTERNAL_STORAGE); } }
*/
// Set up notification manager
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
@Override
protected void onResume() {
super.onResume();
AndTweetService.v(TAG, "onResume");
if (TwitterUser.getTwitterUser().getCredentialsVerified() == CredentialsVerified.NEVER) {
AndTweetService.v(TAG, "Finishing this Activity because user was not ever authenticated");
mIsFinishingOnResume = true;
finish();
} else if (AndTweetPreferences.getDefaultSharedPreferences().getLong(PreferencesActivity.KEY_PREFERENCES_CHANGE_TIME, 0) > preferencesChangeTime) {
mIsFinishingOnResume = true;
AndTweetService.v(TAG, "Restarting the activity to apply any new changes");
finish();
switchTimelineActivity(mTimelineType);
}
if (!mIsFinishingOnResume) {
bindToService();
updateTitle();
restorePosition();
}
}
/**
* Save Position per User and per TimeleneType Actually we save two Item
* IDs: firstItemId - the first visible Tweet lastItemId - the last tweet we
* should retrieve before restoring position
*/
private void savePosition() {
long firstItemId = 0;
long lastItemId = 0;
int firstScrollPos = getListView().getFirstVisiblePosition();
int lastScrollPos = -1;
android.widget.ListAdapter la = getListView().getAdapter();
if (firstScrollPos >= la.getCount() - 1) {
// Skip footer
firstScrollPos = la.getCount() - 2;
}
if (firstScrollPos >= 0) {
// for (int ind =0; ind < la.getCount(); ind++) {
// Log.v(TAG, "itemId[" + ind + "]=" + la.getItemId(ind));
// }
firstItemId = la.getItemId(firstScrollPos);
// We will load one more "page of tweets" below (older) current top
// item
lastScrollPos = firstScrollPos + PAGE_SIZE;
if (lastScrollPos >= la.getCount() - 1) {
// Skip footer
lastScrollPos = la.getCount() - 2;
}
// Log.v(TAG, "lastScrollPos=" + lastScrollPos);
if (lastScrollPos >= 0) {
lastItemId = la.getItemId(lastScrollPos);
} else {
lastItemId = firstItemId;
}
}
if (firstItemId > 0) {
TwitterUser tu = TwitterUser.getTwitterUser();
// Variant 2 is overkill... but let's try...
// I have a feeling that saving preferences while finishing activity sometimes doesn't work...
// - Maybe this was fixed introducing AndTweetPreferences class?!
boolean saveSync = true;
boolean saveAsync = false;
if (saveSync) {
// 1. Synchronous saving
tu.getSharedPreferences().edit().putLong(positionKey(false), firstItemId)
.putLong(positionKey(true), lastItemId).commit();
if (mSearchMode) {
// Remember query string for which the position was saved
tu.getSharedPreferences().edit().putString(positionQueryStringKey(), mQueryString)
.commit();
}
}
if (saveAsync) {
// 2. Asynchronous saving of user's preferences
sendCommand(new CommandData(positionKey(false), firstItemId, tu.getUsername()));
sendCommand(new CommandData(positionKey(true), lastItemId, tu.getUsername()));
if (mSearchMode) {
// Remember query string for which the position was saved
sendCommand(new CommandData(positionQueryStringKey(), mQueryString, tu.getUsername()));
}
}
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "Saved position " + tu.getUsername() + "; " + positionKey(false) + "="
+ firstItemId + "; index=" + firstScrollPos + "; lastId="
+ lastItemId + "; index=" + lastScrollPos);
}
}
}
/**
* Restore (First visible item) position saved for this user and for this type of timeline
*/
private void restorePosition() {
TwitterUser tu = TwitterUser.getTwitterUser();
boolean loaded = false;
long firstItemId = -3;
try {
int scrollPos = -1;
firstItemId = getSavedPosition(false);
if (firstItemId > 0) {
scrollPos = listPosForId(firstItemId);
}
if (scrollPos >= 0) {
getListView().setSelectionFromTop(scrollPos, 0);
loaded = true;
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "Restored position " + tu.getUsername() + "; " + positionKey(false) + "="
+ firstItemId +"; list index=" + scrollPos);
}
} else {
// There is no stored position
if (mSearchMode) {
// In search mode start from the most recent tweet!
scrollPos = 0;
} else {
scrollPos = getListView().getCount() - 2;
}
if (scrollPos >= 0) {
setSelectionAtBottom(scrollPos);
}
}
} catch (Exception e) {
Editor ed = tu.getSharedPreferences().edit();
ed.remove(positionKey(false));
ed.commit();
firstItemId = -2;
}
if (!loaded && Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "Didn't restore position " + tu.getUsername() + "; " + positionKey(false) + "="
+ firstItemId);
}
positionRestored = true;
}
/**
* @param lastRow Key for First visible row (false) or Last row that will be retrieved (true)
* @return Saved Tweet id or < 0 if none was found.
*/
protected long getSavedPosition(boolean lastRow) {
TwitterUser tu = TwitterUser.getTwitterUser();
long savedItemId = -3;
if (!mSearchMode
|| (mQueryString.compareTo(tu.getSharedPreferences().getString(
positionQueryStringKey(), "")) == 0)) {
// Load saved position in Search mode only if that position was
// saved for the same query string
savedItemId = tu.getSharedPreferences().getLong(positionKey(lastRow), -1);
}
return savedItemId;
}
/**
* Two rows are store for each position:
* @param lastRow Key for First visible row (false) or Last row that will be retrieved (true)
* @return Key to store position (tweet id of the first visible row)
*/
private String positionKey(boolean lastRow) {
return LAST_POS_KEY + mTimelineType + (mSearchMode ? "_search" : "") + (lastRow ? "_last" : "");
}
/**
* @return Key to store query string for this position
*/
private String positionQueryStringKey() {
return LAST_POS_KEY + mTimelineType + "_querystring";
}
private void setSelectionAtBottom(int scrollPos) {
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "setSelectionAtBottom, 1");
}
int viewHeight = getListView().getHeight();
int childHeight;
childHeight = 30;
int y = viewHeight - childHeight;
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "set position of last item to " + y + "px");
}
getListView().setSelectionFromTop(scrollPos, y);
}
/**
* Returns the position of the item with the given ID.
*
* @param searchedId the ID of the item whose position in the list is to be
* returned.
* @return the position in the list or -1 if the item was not found
*/
private int listPosForId(long searchedId) {
int listPos;
boolean itemFound = false;
ListView lv = getListView();
int itemCount = lv.getCount();
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "item count: " + itemCount);
}
for (listPos = 0; (!itemFound && (listPos < itemCount)); listPos++) {
long itemId = lv.getItemIdAtPosition(listPos);
if (itemId == searchedId) {
itemFound = true;
break;
}
}
if (!itemFound) {
listPos = -1;
}
return listPos;
}
@Override
public void onContentChanged() {
super.onContentChanged();
if (Log.isLoggable(AndTweetService.APPTAG, Log.DEBUG)) {
Log.d(TAG, "Content changed");
}
}
@Override
protected void onPause() {
super.onPause();
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "onPause");
}
// The activity just lost its focus,
// so we have to start notifying the User about new events after his
// moment.
if (!mIsFinishingOnResume) {
// Get rid of the "fast scroll thumb"
((ListView) findViewById(android.R.id.list)).setFastScrollEnabled(false);
clearNotifications();
savePosition();
}
positionRestored = false;
disconnectService();
}
/**
* Cancel notifications of loading timeline
* They were set in
* @see com.xorcode.andtweet.AndTweetService.CommandExecutor#notifyNewTweets(int, CommandEnum)
*/
private void clearNotifications() {
try {
// TODO: Check if there are any notifications
// and if none than don't waist time for this:
mNM.cancel(AndTweetService.CommandEnum.NOTIFY_TIMELINE.ordinal());
mNM.cancel(AndTweetService.CommandEnum.NOTIFY_REPLIES.ordinal());
mNM.cancel(AndTweetService.CommandEnum.NOTIFY_DIRECT_MESSAGE.ordinal());
// Reset notifications on AppWidget(s)
Intent intent = new Intent(AndTweetService.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AndTweetService.EXTRA_MSGTYPE, AndTweetService.CommandEnum.NOTIFY_CLEAR.save());
sendBroadcast(intent);
} finally {
// Nothing yet...
}
}
@Override
public void onDestroy() {
super.onDestroy();
disconnectService();
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_AUTHENTICATION_FAILED:
return new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.dialog_title_authentication_failed).setMessage(
R.string.dialog_summary_authentication_failed).setPositiveButton(
android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface Dialog, int whichButton) {
startActivity(new Intent(TimelineActivity.this,
PreferencesActivity.class));
}
}).create();
case DIALOG_SERVICE_UNAVAILABLE:
return new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.dialog_title_service_unavailable).setMessage(
R.string.dialog_summary_service_unavailable).setPositiveButton(
android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface Dialog, int whichButton) {
}
}).create();
case DIALOG_EXTERNAL_STORAGE:
return new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_info)
.setTitle(R.string.dialog_title_external_storage).setMessage(
R.string.dialog_summary_external_storage).setPositiveButton(
android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface Dialog, int whichButton) {
SharedPreferences.Editor editor = AndTweetPreferences.getDefaultSharedPreferences().edit();
editor.putBoolean("confirmed_external_storage_use", true);
editor.putBoolean("storage_use_external", true);
editor.commit();
destroyService();
finish();
Intent intent = new Intent(TimelineActivity.this,
TweetListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}).setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface Dialog, int whichButton) {
SharedPreferences.Editor editor = AndTweetPreferences.getDefaultSharedPreferences().edit();
editor.putBoolean("confirmed_external_storage_use", true);
editor.commit();
}
}).create();
case DIALOG_EXTERNAL_STORAGE_MISSING:
return new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.dialog_title_external_storage_missing).setMessage(
R.string.dialog_summary_external_storage_missing)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface Dialog, int whichButton) {
SharedPreferences.Editor editor = AndTweetPreferences.getDefaultSharedPreferences().edit();
editor.putBoolean("confirmed_external_storage_use", true);
editor.putBoolean("storage_use_external", false);
editor.commit();
destroyService();
finish();
Intent intent = new Intent(TimelineActivity.this,
TweetListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}).setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface Dialog, int whichButton) {
destroyService();
finish();
}
}).create();
case DIALOG_CONNECTION_TIMEOUT:
return new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.dialog_title_connection_timeout).setMessage(
R.string.dialog_summary_connection_timeout).setPositiveButton(
android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface Dialog, int whichButton) {
}
}).create();
case DIALOG_EXECUTING_COMMAND:
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setIcon(android.R.drawable.ic_dialog_info);
mProgressDialog.setTitle(R.string.dialog_title_executing_command);
mProgressDialog.setMessage(getText(R.string.dialog_summary_executing_command));
return mProgressDialog;
default:
return super.onCreateDialog(id);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.timeline, menu);
Intent intent = new Intent(null, getIntent().getData());
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, new ComponentName(this,
TweetListActivity.class), null, intent, 0, null);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.preferences_menu_id:
startPreferencesActivity();
break;
case R.id.favorites_timeline_menu_id:
switchTimelineActivity(Tweets.TIMELINE_TYPE_FAVORITES);
break;
case R.id.friends_timeline_menu_id:
switchTimelineActivity(Tweets.TIMELINE_TYPE_FRIENDS);
break;
case R.id.direct_messages_menu_id:
switchTimelineActivity(Tweets.TIMELINE_TYPE_MESSAGES);
break;
case R.id.search_menu_id:
onSearchRequested();
break;
case R.id.mentions_menu_id:
switchTimelineActivity(Tweets.TIMELINE_TYPE_MENTIONS);
break;
case R.id.reload_menu_item:
manualReload();
break;
}
return super.onOptionsItemSelected(item);
}
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
}
public boolean onKey(View v, int keyCode, KeyEvent event) {
return false;
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_IDLE:
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
break;
case OnScrollListener.SCROLL_STATE_FLING:
// Turn the "fast scroll thumb" on
view.setFastScrollEnabled(true);
break;
}
}
/**
* Load the theme for preferences.
*/
public void loadTheme() {
boolean light = AndTweetPreferences.getDefaultSharedPreferences().getBoolean("appearance_light_theme", false);
StringBuilder theme = new StringBuilder();
String name = AndTweetPreferences.getDefaultSharedPreferences().getString("theme", "AndTweet");
if (name.indexOf("Theme.") > -1) {
name = name.substring(name.indexOf("Theme."));
}
theme.append("Theme.");
if (light) {
theme.append("Light.");
}
theme.append(name);
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "loadTheme; theme=\"" + theme.toString() + "\"");
}
setTheme((int) getResources().getIdentifier(theme.toString(), "style",
"com.xorcode.andtweet"));
}
/**
* Updates the activity title.
* Sets the title with a left and right title.
*
* @param rightText Right title part
*/
public void updateTitle(String rightText) {
String timelinename = "??";
switch (mTimelineType) {
case Tweets.TIMELINE_TYPE_FAVORITES:
timelinename = getString(R.string.activity_title_favorites);
break;
case Tweets.TIMELINE_TYPE_FRIENDS:
timelinename = getString(R.string.activity_title_timeline);
break;
case Tweets.TIMELINE_TYPE_MENTIONS:
timelinename = getString(R.string.activity_title_mentions);
break;
case Tweets.TIMELINE_TYPE_MESSAGES:
timelinename = getString(R.string.activity_title_direct_messages);
break;
}
String username = AndTweetPreferences.getDefaultSharedPreferences().getString(PreferencesActivity.KEY_TWITTER_USERNAME, null);
String leftText = getString(R.string.activity_title_format, new Object[] {
timelinename, username + (mSearchMode ? " *" : "")
});
TextView leftTitle = (TextView) findViewById(R.id.custom_title_left_text);
TextView rightTitle = (TextView) findViewById(R.id.custom_title_right_text);
leftTitle.setText(leftText);
rightTitle.setText(rightText);
}
public void updateTitle() {
updateTitle("");
}
/**
* Retrieve the text that is currently in the editor.
*
* @return Text currently in the editor
*/
protected CharSequence getSavedText() {
return ((EditText) findViewById(R.id.edtTweetInput)).getText();
}
/**
* Set the text in the text editor.
*
* @param text
*/
protected void setSavedText(CharSequence text) {
((EditText) findViewById(R.id.edtTweetInput)).setText(text);
}
/**
* Initialize the user interface.
*/
protected void initUI() {
// Attach listeners to the message list
getListView().setOnCreateContextMenuListener(this);
getListView().setOnItemClickListener(this);
}
/**
* Check to see if the system has a hardware keyboard.
*
* @return
*/
protected boolean hasHardwareKeyboard() {
Configuration c = getResources().getConfiguration();
switch (c.keyboard) {
case Configuration.KEYBOARD_12KEY:
case Configuration.KEYBOARD_QWERTY:
return true;
default:
return false;
}
}
/**
* Initialize service and bind to it.
*/
protected void bindToService() {
if (!mIsBound) {
mIsBound = true;
Intent serviceIntent = new Intent(IAndTweetService.class.getName());
if (!AndTweetServiceManager.isStarted()) {
// Ensure that AndTweetService is running
AndTweetServiceManager.startAndTweetService(this, new CommandData(CommandEnum.EMPTY));
}
// startService(serviceIntent);
bindService(serviceIntent, mServiceConnection, 0);
}
}
/**
* Disconnect and unregister the service.
*/
protected void disconnectService() {
if (mIsBound) {
if (mService != null) {
try {
mService.unregisterCallback(mServiceCallback);
} catch (RemoteException e) {
// Service crashed, not much we can do.
}
mService = null;
}
unbindService(mServiceConnection);
//AndTweetServiceManager.stopAndTweetService(this);
mIsBound = false;
}
}
/**
* Disconnects from the service and stops it.
*/
protected void destroyService() {
disconnectService();
stopService(new Intent(IAndTweetService.class.getName()));
mService = null;
mIsBound = false;
}
/**
* Service connection handler.
*/
private ServiceConnection mServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "onServiceConnected");
}
mService = IAndTweetService.Stub.asInterface(service);
// We want to monitor the service for as long as we are
// connected to it.
try {
mService.registerCallback(mServiceCallback);
// Push the queue
sendCommand(null);
} catch (RemoteException e) {
// Service has already crashed, nothing much we can do
// except hope that it will restart.
}
}
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
/**
* Intents queue to be send to the AndTweetService
*/
private BlockingQueue<CommandData> mCommands = new ArrayBlockingQueue<CommandData>(100, true);
/**
* Send broadcast with the command (in the form of Intent) to the AndTweet Service after it will be
* connected to this activity. We should wait for the connection because
* otherwise we won't receive callback from the service
*
* Plus this method restarts this Activity if command is PUT_BOOLEAN_PREFERENCE with KEY_PREFERENCES_CHANGE_TIME
*
* @param commandData Intent to send, null if we only want to push the queue
*/
protected synchronized void sendCommand(CommandData commandData) {
if (commandData != null) {
if (!mCommands.contains(commandData)) {
if (!mCommands.offer(commandData)) {
Log.e(TAG, "mCommands is full?");
}
}
}
if (mService != null) {
// Service is connected, so we can send queued Intents
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "Sendings " + mCommands.size() + " broadcasts");
}
while (true) {
AndTweetService.CommandData element = mCommands.poll();
if (element == null) { break; }
sendBroadcast(element.toIntent());
}
}
}
/**
* Service callback handler.
*/
protected IAndTweetServiceCallback mServiceCallback = new IAndTweetServiceCallback.Stub() {
/**
* Tweets changed callback method
*
* @param value
* @throws RemoteException
*/
public void tweetsChanged(int value) throws RemoteException {
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "tweetsChanged value=" + value);
}
mHandler.sendMessage(mHandler.obtainMessage(MSG_TWEETS_CHANGED, value, 0));
}
/**
* dataLoading callback method
*
* @param value
* @throws RemoteException
*/
public void dataLoading(int value) throws RemoteException {
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "dataLoading value=" + value);
}
mHandler.sendMessage(mHandler.obtainMessage(MSG_DATA_LOADING, value, 0));
}
/**
* Messages changed callback method
*
* @param value
* @throws RemoteException
*/
public void messagesChanged(int value) throws RemoteException {
mHandler.sendMessage(mHandler.obtainMessage(MSG_DIRECT_MESSAGES_CHANGED, value, 0));
}
/**
* Replies changed callback method
*
* @param value
* @throws RemoteException
*/
public void repliesChanged(int value) throws RemoteException {
mHandler.sendMessage(mHandler.obtainMessage(MSG_REPLIES_CHANGED, value, 0));
}
public void rateLimitStatus(int remaining_hits, int hourly_limit) throws RemoteException {
mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATED_TITLE, remaining_hits, hourly_limit));
}
};
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "onNewIntent");
}
setTimelineType(intent);
}
private void setTimelineType(Intent intentNew) {
int timelineType_new = intentNew.getIntExtra(AndTweetService.EXTRA_TIMELINE_TYPE,
Tweets.TIMELINE_TYPE_NONE);
if (timelineType_new != Tweets.TIMELINE_TYPE_NONE) {
mTimelineType = timelineType_new;
}
mQueryString = intentNew.getStringExtra(SearchManager.QUERY);
mSearchMode = (mQueryString != null && mQueryString.length() > 0);
if (mSearchMode) {
// Let's check if last time we saved position for the same query
// string
} else {
mQueryString = "";
}
if (mTimelineType == Tweets.TIMELINE_TYPE_NONE) {
mTimelineType = Tweets.TIMELINE_TYPE_FRIENDS;
// For some reason Android remembers last Query and adds it even if
// the Activity was started from the Widget...
Intent intent = getIntent();
intent.removeExtra(SearchManager.QUERY);
intent.removeExtra(SearchManager.APP_DATA);
intent.putExtra(AndTweetService.EXTRA_TIMELINE_TYPE, mTimelineType);
intent.setData(AndTweetDatabase.Tweets.CONTENT_URI);
}
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "setTimelineType; type=\"" + mTimelineType + "\"");
}
}
protected void manualReload() {
// Only newer tweets (newer that last loaded) are being loaded
// from the Internet,
// old tweets are not being reloaded.
// Show something to the user...
mListFooter.setVisibility(View.VISIBLE);
// Ask service to load data for this mTimelineType
AndTweetService.CommandEnum command = CommandEnum.FETCH_TIMELINE;
switch (mTimelineType) {
case Tweets.TIMELINE_TYPE_MESSAGES:
command = CommandEnum.FETCH_MESSAGES;
}
sendCommand( new CommandData(command));
}
protected void startPreferencesActivity() {
// We need to restart this Activity after exiting PreferencesActivity
// So let's set the flag:
//AndTweetPreferences.getDefaultSharedPreferences().edit()
// .putBoolean(PreferencesActivity.KEY_PREFERENCES_CHANGE_TIME, true).commit();
startActivity(new Intent(this, PreferencesActivity.class));
}
/**
* Switch type of presented timeline
*/
protected void switchTimelineActivity(int timelineType) {
Intent intent;
switch (timelineType) {
case Tweets.TIMELINE_TYPE_MESSAGES:
intent = new Intent(this, MessageListActivity.class);
Bundle appDataBundle = new Bundle();
appDataBundle.putParcelable("content_uri",
AndTweetDatabase.DirectMessages.CONTENT_URI);
intent.putExtra(SearchManager.APP_DATA, appDataBundle);
break;
default:
timelineType = Tweets.TIMELINE_TYPE_FRIENDS;
case Tweets.TIMELINE_TYPE_MENTIONS:
case Tweets.TIMELINE_TYPE_FAVORITES:
case Tweets.TIMELINE_TYPE_FRIENDS:
intent = new Intent(this, TweetListActivity.class);
break;
}
intent.removeExtra(SearchManager.QUERY);
intent.putExtra(AndTweetService.EXTRA_TIMELINE_TYPE, timelineType);
// We don't use the Action anywhere, so there is no need it setting it.
// - we're analyzing query instead!
// intent.setAction(Intent.ACTION_SEARCH);
startActivity(intent);
}
}