blob: 731128a7edf2d85b3a1e9db9337f9ac297f7eb92 [file] [log] [blame]
/*
* Copyright (C) 2008 Google Inc.
*
* 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.android.im.app;
import java.util.ArrayList;
import java.util.List;
import com.android.im.R;
import com.android.im.plugin.BrandingResourceIDs;
import com.android.im.provider.Imps;
import android.app.Activity;
import android.app.Dialog;
import android.content.AsyncQueryHandler;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.ContentResolver;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.view.Window;
import android.widget.ImageView;
import android.widget.CursorAdapter;
import android.widget.TextView;
import android.os.Bundle;
import android.os.Handler;
public class ChatSwitcher {
private static final boolean LOCAL_DEBUG = true;
private static final String[] PROVIDER_CATEGORY_PROJECTION = new String[] {
Imps.Provider.CATEGORY
};
private static final int PROVIDER_CATEGORY_COLUMN = 0;
private boolean mPreferMenuShortcut = false;
public SwitcherAdapter mSwitcherAdapter;
private Activity mActivity;
private Handler mHandler;
private ImApp mApp;
private String mQuerySelection;
private String mQuerySelectionArgs[];
private LayoutInflater mInflater;
private SwitcherRunnable mSwitcherCallback;
private ArrayList<View> mViews = new ArrayList<View>();
private boolean mOkToShowEmptyView;
private ChatSwitcherDialog mChatSwitcherDialog;
private AsyncQueryHandler mQueryHandler;
private static final int sQueryToken = 1;
private static final long sPeriodicUpdatePeriod = 60000;
public int mContactIdColumn;
public int mProviderIdColumn;
public int mAccountIdColumn;
public int mUsernameColumn;
public int mNicknameColumn;
public int mAvatarDataColumn;
public int mPresenceStatusColumn;
public int mLastUnreadMessageColumn;
public int mShortcutColumn;
public int mLastChatColumn;
public int mGroupChatColumn;
public interface OnQueryCompleteRunnable {
public void onComplete(Cursor c);
}
public interface SwitcherRunnable {
public boolean switchTo(String contact, long account, Intent intent);
}
private DataSetObserver mObserver = new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
if (isOpen()) {
if (LOCAL_DEBUG) log("Observer.onChanged: update");
update();
}
}
@Override
public void onInvalidated() {
super.onInvalidated();
if (isOpen()) {
if (LOCAL_DEBUG) log("Observer.onInvalidated: update");
update();
}
}
};
private void cancelPreviousQuery() {
mQueryHandler.cancelOperation(sQueryToken);
}
private void startQuery() {
startQuery(new OnQueryCompleteRunnable() {
public void onComplete(Cursor c) {
mSwitcherAdapter.changeCursor(c);
}
});
}
private void startQuery(OnQueryCompleteRunnable runnable) {
cancelPreviousQuery();
mQueryHandler.startQuery(
sQueryToken,
runnable,
Imps.Contacts.CONTENT_URI_CHAT_CONTACTS,
null, /*projection*/
mQuerySelection,
mQuerySelectionArgs,
null /*orderBy*/);
}
/**
* Onscreen dialog used to show the chat switcher
*/
private class ChatSwitcherDialog extends Dialog {
public ChatSwitcher mSwitcher;
private ViewGroup mContainer;
private View mEmptyView;
public ChatSwitcherDialog(Context context, ChatSwitcher switcher) {
super(context, R.style.Theme_ChatSwitcher);
mSwitcher = switcher;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.chat_switcher_dialog);
mContainer = (ViewGroup) findViewById(R.id.pseudogallery);
mEmptyView = findViewById(R.id.empty);
}
public void updateTimes() {
final Cursor c = mSwitcherAdapter.getCursor();
if (c != null) {
c.moveToPosition(-1);
while (c.moveToNext()) {
if (!mSwitcherAdapter.validateCursor(c)) {
if (LOCAL_DEBUG) log("populateGallery: validate cursor failed");
return; // return true so we don't draw the empty text
}
View v = mViews.get(c.getPosition());
mSwitcherAdapter.bindView(v, mActivity, c);
}
}
}
private void updateViewListeners(View v, final Cursor c, final int position) {
v.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
select(c, position);
}});
v.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return false;
}
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_DPAD_CENTER:
select(c, position);
return true;
}
return false;
}
});
}
public void update() {
int focusPos = 0;
int sz = mViews.size();
for (int i = 0; i < sz; i++) {
View v = mViews.get(i);
if (v == null) {
continue;
}
if (v.getVisibility() != View.VISIBLE) {
continue;
}
if (v.hasFocus()) {
focusPos = i;
break;
}
}
int pos = 0;
final Cursor c = mSwitcherAdapter.getCursor();
if (c != null) {
c.moveToPosition(-1);
while (c.moveToNext()) {
if (!mSwitcherAdapter.validateCursor(c)) {
if (LOCAL_DEBUG) log("populateGallery: validate cursor failed");
return; // return true so we don't draw the empty text
}
View v;
if (pos >= mViews.size()) {
mInflater.inflate(R.layout.chat_switcher_item, mContainer, true);
v = mContainer.getChildAt(mContainer.getChildCount() - 1);
mViews.add(v);
v.setFocusable(true);
} else {
v = mViews.get(pos);
}
updateViewListeners(v, c, pos);
v.setVisibility(View.VISIBLE);
mSwitcherAdapter.bindView(v, mActivity, c);
v.setTag(c.getLong(mProviderIdColumn));
pos++;
}
int viewCount = mViews.size();
int cursorCount = c.getCount();
for (int i = cursorCount; i <viewCount; i++) {
mViews.get(i).setVisibility(View.GONE);
}
if (focusPos < mViews.size()) {
mViews.get(focusPos).requestFocus();
}
}
mEmptyView.setVisibility(pos > 0 || !mOkToShowEmptyView ? View.GONE : View.VISIBLE);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_BACK:
if (event.getAction() == KeyEvent.ACTION_DOWN) {
mSwitcher.close();
return true;
}
break;
case KeyEvent.KEYCODE_MENU:
// if we don't have a keyboard then we don't use the
// fancy keyboard shortcuts for the chat switcher
if (0 != (mActivity.getResources().getConfiguration().keyboard &
Configuration.KEYBOARD_QWERTY)) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
mSwitcher.mPreferMenuShortcut = true;
mSwitcher.update();
} else if (event.getAction() == KeyEvent.ACTION_UP) {
mSwitcher.mPreferMenuShortcut = false;
mSwitcher.update();
}
}
return true;
case KeyEvent.KEYCODE_0:
mSwitcher.handleShortcut('0');
break;
case KeyEvent.KEYCODE_1:
mSwitcher.handleShortcut('1');
break;
case KeyEvent.KEYCODE_2:
mSwitcher.handleShortcut('2');
break;
case KeyEvent.KEYCODE_3:
mSwitcher.handleShortcut('3');
break;
case KeyEvent.KEYCODE_4:
mSwitcher.handleShortcut('4');
break;
case KeyEvent.KEYCODE_5:
mSwitcher.handleShortcut('5');
break;
case KeyEvent.KEYCODE_6:
mSwitcher.handleShortcut('6');
break;
case KeyEvent.KEYCODE_7:
mSwitcher.handleShortcut('7');
break;
case KeyEvent.KEYCODE_8:
mSwitcher.handleShortcut('8');
break;
case KeyEvent.KEYCODE_9:
mSwitcher.handleShortcut('9');
break;
}
return super.dispatchKeyEvent(event);
}
}
/**
* The cursor adapter for the chatting contacts
*/
public class SwitcherAdapter extends CursorAdapter {
private String mMenuPlus;
private int mLayout;
private android.database.ContentObserver mContentObserver =
new android.database.ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
if (isOpen())
startQuery();
}
};
public SwitcherAdapter(Cursor c, Activity a) {
// use false as the third parameter to the CursorAdapter constructor
// to indicate that we should not auto-requery the cursor
super(a, c, false);
mLayout = R.layout.chat_switcher_item;
mActivity = a;
mMenuPlus = a.getString(R.string.menu_plus);
setupObservers(null);
}
private void setupObservers(Cursor oldCursor) {
if (oldCursor != null) {
oldCursor.unregisterContentObserver(mContentObserver);
}
Cursor c = getCursor();
if (c == null)
return;
c.registerContentObserver(mContentObserver);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(mLayout, parent, false);
}
@Override
public void changeCursor(Cursor c) {
Cursor oldCursor = getCursor();
super.changeCursor(c);
setupObservers(oldCursor);
}
public boolean validateCursor(Cursor c) {
long accountId = c.getLong(mAccountIdColumn);
return accountId != 0;
}
@Override
public void bindView(View view, Context context, Cursor c) {
long providerId = c.getLong(mProviderIdColumn);
String nickname = c.getString(mNicknameColumn);
//if (LOCAL_DEBUG) log("bindView: nickname=" + nickname);
TextView t = (TextView) view.findViewById(R.id.name);
t.setText(nickname);
int presenceMode = c.getInt(mPresenceStatusColumn);
String s = c.getString(mLastUnreadMessageColumn);
BrandingResources brandingRes = mApp.getBrandingResource(providerId);
// If there is unread text, indicate that, otherwise, show presence
Drawable presence;
if (s != null) {
presence = brandingRes.getDrawable(
BrandingResourceIDs.DRAWABLE_UNREAD_CHAT);
} else {
presence = brandingRes.getDrawable(
PresenceUtils.getStatusIconId(presenceMode));
}
ImageView presenceView = (ImageView) view.findViewById(R.id.presence);
presenceView.setImageDrawable(presence);
// If there is a shortcut assigned to this chat, then show it, otherwise
// hide the shortcut text view.
long shortcut = c.getLong(mShortcutColumn);
TextView shortcutView = (TextView) view.findViewById(R.id.shortcut);
shortcutView.setVisibility(mPreferMenuShortcut ? View.VISIBLE : View.GONE);
if (shortcut >= 0 && shortcut < 10) {
shortcutView.setText(mMenuPlus + shortcut);
} else {
shortcutView.setText("");
}
ImageView avatarView = (ImageView) view.findViewById(R.id.avatar);
Drawable avatar = DatabaseUtils.getAvatarFromCursor(c, mAvatarDataColumn);
if (avatar == null) {
avatarView.setImageResource(R.drawable.avatar_unknown);
} else {
avatarView.setImageDrawable(avatar);
}
TextView tv = (TextView) view.findViewById(R.id.when);
tv.setText(android.text.format.DateUtils.getRelativeTimeSpanString(
c.getLong(mLastChatColumn),
System.currentTimeMillis(),
android.text.format.DateUtils.MINUTE_IN_MILLIS,
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE));
tv.setVisibility(mPreferMenuShortcut ? View.GONE : View.VISIBLE);
}
}
public ChatSwitcher(
final Activity a,
final Handler handler,
final ImApp app,
final LayoutInflater inflater,
final SwitcherRunnable switcher) {
mActivity = a;
mApp = app;
mHandler = handler;
mInflater = inflater;
mSwitcherCallback = switcher;
buildQueryParams();
mSwitcherAdapter = new SwitcherAdapter(null, a);
mQueryHandler = new AsyncQueryHandler(mActivity.getContentResolver()) {
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
if (cursor != null) {
mContactIdColumn = cursor.getColumnIndexOrThrow(Imps.Contacts._ID);
mProviderIdColumn = cursor.getColumnIndexOrThrow(Imps.Contacts.PROVIDER);
mAccountIdColumn = cursor.getColumnIndexOrThrow(Imps.Contacts.ACCOUNT);
mUsernameColumn = cursor.getColumnIndexOrThrow(Imps.Contacts.USERNAME);
mNicknameColumn = cursor.getColumnIndexOrThrow(Imps.Contacts.NICKNAME);
mPresenceStatusColumn = cursor.getColumnIndexOrThrow(Imps.Contacts.PRESENCE_STATUS);
mLastUnreadMessageColumn = cursor.getColumnIndexOrThrow(Imps.Chats.LAST_UNREAD_MESSAGE);
mAvatarDataColumn = cursor.getColumnIndexOrThrow(Imps.Contacts.AVATAR_DATA);
mShortcutColumn = cursor.getColumnIndexOrThrow(Imps.Chats.SHORTCUT);
mLastChatColumn = cursor.getColumnIndexOrThrow(Imps.Chats.LAST_MESSAGE_DATE);
mGroupChatColumn = cursor.getColumnIndexOrThrow(Imps.Chats.GROUP_CHAT);
}
mOkToShowEmptyView = true;
update();
OnQueryCompleteRunnable r = (OnQueryCompleteRunnable) cookie;
if (r != null) {
r.onComplete(cursor);
}
}
@Override
protected void onUpdateComplete(int token, Object cookie, int result) {
super.onUpdateComplete(token, cookie, result);
}
};
};
private void buildQueryParams() {
StringBuilder buf = new StringBuilder("(");
List<ProviderDef> list = mApp.getProviders();
mQuerySelectionArgs = new String[list.size()];
int i = 0;
for (ProviderDef providerDef : list) {
//Log.i(ImApp.LOG_TAG, "[ChatSwitcher] buildQueryParams: provider " + providerDef.mName +
// ", " + providerDef.mId);
if (i > 0) {
buf.append(" OR ");
}
buf.append(Imps.Contacts.PROVIDER).append("=?");
mQuerySelectionArgs[i] = String.valueOf(providerDef.mId);
i++;
}
buf.append(')');
mQuerySelection = buf.toString();
//Log.i(ImApp.LOG_TAG, "[ChatSwitcher] buildQueryParams: selection => " + mQuerySelection);
}
private static String findCategory(ContentResolver resolver, long providerId) {
// find the provider category for this chat
Cursor providerCursor = resolver.query(
Imps.Provider.CONTENT_URI,
PROVIDER_CATEGORY_PROJECTION,
"_id = " + providerId,
null /* selection args */,
null /* sort order */
);
String category = null;
try {
if (providerCursor.moveToFirst()) {
category = providerCursor.getString(PROVIDER_CATEGORY_COLUMN);
}
} finally {
providerCursor.close();
}
return category;
}
public static Intent makeChatIntent(ContentResolver resolver, long provider, long account,
String contact, long contactId, int groupChat) {
Intent i = new Intent(Intent.ACTION_VIEW,
ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, contactId));
i.addCategory(findCategory(resolver, provider));
i.putExtra("from", contact);
i.putExtra("providerId", provider);
i.putExtra("accountId", account);
if (groupChat != 0) {
i.putExtra("groupChat", groupChat);
}
return i;
}
public void open() {
mChatSwitcherDialog = new ChatSwitcherDialog(mActivity, this);
mChatSwitcherDialog.show();
mOkToShowEmptyView = false;
mPreferMenuShortcut = false;
mSwitcherAdapter.registerDataSetObserver(mObserver);
startQuery();
mHandler.postDelayed(mSimpleUpdateRunnable, sPeriodicUpdatePeriod);
}
private Runnable mSimpleUpdateRunnable = new Runnable() {
public void run() {
if (mActivity.isFinishing()) {
return;
}
if (!isOpen()) {
return;
}
if (mChatSwitcherDialog != null) {
mChatSwitcherDialog.updateTimes();
}
mHandler.postDelayed(this, sPeriodicUpdatePeriod);
}
};
public void close() {
if (isOpen()) {
mChatSwitcherDialog.dismiss();
mChatSwitcherDialog = null;
// if we're in the midst of querying then don't
cancelPreviousQuery();
// clean up the adapter, close cursors, etc.
mSwitcherAdapter.unregisterDataSetObserver(mObserver);
mSwitcherAdapter.changeCursor(null);
mViews.clear();
mHandler.removeCallbacks(mSimpleUpdateRunnable);
}
}
public void onResume() {
if (isOpen() && !mActivity.isFinishing()) {
mChatSwitcherDialog.updateTimes();
}
}
private void update() {
if (mChatSwitcherDialog != null) {
mChatSwitcherDialog.update();
}
}
public void select(Cursor c, int position) {
if (!c.moveToPosition(position)) {
Log.e(ImApp.LOG_TAG, "select: moved to pos=" + position + " failed");
return;
}
long contactId = c.getLong(mContactIdColumn);
String contact = c.getString(mUsernameColumn);
long accountId = c.getLong(mAccountIdColumn);
long providerId = c.getLong(mProviderIdColumn);
int groupChat = c.getInt(mGroupChatColumn);
Intent intent = ChatSwitcher.makeChatIntent(
mActivity.getContentResolver(), providerId, accountId,
contact, contactId, groupChat);
if (mSwitcherCallback == null || !mSwitcherCallback.switchTo(contact, accountId, intent)) {
mActivity.startActivity(intent);
mActivity.finish();
}
close();
}
public boolean isOpen() {
return mChatSwitcherDialog != null;
}
private void handleShortcut(Cursor c, int key) {
if (mActivity.isFinishing()) {
return;
}
c.moveToPosition(-1);
while (c.moveToNext()) {
long shortcut = c.getLong(mShortcutColumn);
if (shortcut >= 0 && shortcut < 10 && key == (shortcut + '0')) {
select(c, c.getPosition());
break;
}
}
}
public void handleShortcut(final int key) {
if (mSwitcherAdapter.getCursor() != null) {
handleShortcut(mSwitcherAdapter.getCursor(), key);
} else {
startQuery(new OnQueryCompleteRunnable() {
public void onComplete(Cursor c) {
handleShortcut(c, key);
c.close();
}
});
}
}
private int findCurrent(Cursor c, String contact, long accountId, long providerId) {
c.moveToPosition(-1);
while (c.moveToNext()) {
if (c.getLong(mAccountIdColumn) != accountId) {
continue;
}
if (c.getLong(mProviderIdColumn) != providerId) {
continue;
}
if (!c.getString(mUsernameColumn).equals(contact)) {
continue;
}
return c.getPosition();
}
return -1;
}
private void rotateChat(Cursor c, int direction, String contact, long accountId, long providerId) {
int count = c.getCount();
if (count < 2) {
return;
}
int position = findCurrent(c, contact, accountId, providerId);
if (position == -1) {
return;
}
position += direction;
if (position == count) {
position = 0;
} else if (position == -1) {
position = count - 1;
}
select(c, position);
}
/*
* +1 to go forward, -1 to go backward
*/
public void rotateChat(final int direction, final String contact, final long accountId, final long providerId) {
if (direction != 1 && direction != -1) {
return;
}
if (mSwitcherAdapter.getCursor() != null) {
rotateChat(mSwitcherAdapter.getCursor(), direction, contact, accountId, providerId);
} else {
startQuery(new OnQueryCompleteRunnable() {
public void onComplete(Cursor c) {
rotateChat(c, direction, contact, accountId, providerId);
c.close();
}
});
}
}
private void log(String msg) {
Log.d(ImApp.LOG_TAG, "[ChatSwitcher] " + msg);
}
}