/**
* Copyright (C) 2007 The Android Open Source Project
*
* 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.contacts;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Locale;
import android.accounts.Account;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.content.AsyncQueryHandler;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.CharArrayBuffer;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.database.sqlite.SQLiteDiskIOException;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteFullException;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.ContactsContract;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.contacts.util.Config;
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.ITelephony;
/***
* Displays a list of call log entries.
*/
public class MultiSelectCalllogDeleteActivity extends ListActivity
implements View.OnCreateContextMenuListener ,View.OnClickListener{
private static final String TAG = "MultiSelectCalllogDeleteActivity";
private static final boolean DBG = true;
public static final String SIM_INDEX = RecentCallsListActivity.SIM_INDEX;
/*** The projection to use when querying the call log table */
static final String[] CALL_LOG_PROJECTION = new String[] {
Calls._ID,
Calls.NUMBER,
Calls.DATE,
Calls.DURATION,
Calls.TYPE,
Calls.CACHED_NAME,
Calls.CACHED_NUMBER_TYPE,
Calls.CACHED_NUMBER_LABEL,
Calls.PHONE_ID
};
static final int ID_COLUMN_INDEX = 0;
static final int NUMBER_COLUMN_INDEX = 1;
static final int DATE_COLUMN_INDEX = 2;
static final int DURATION_COLUMN_INDEX = 3;
static final int CALL_TYPE_COLUMN_INDEX = 4;
static final int CALLER_NAME_COLUMN_INDEX = 5;
static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 6;
static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 7;
static final int SIM_COLUMN_INDEX = 8;
/*** The projection to use when querying the phones table */
static final String[] PHONES_PROJECTION = new String[] {
PhoneLookup._ID,
PhoneLookup.DISPLAY_NAME,
PhoneLookup.TYPE,
PhoneLookup.LABEL,
PhoneLookup.NUMBER,
ContactsContract.RawContacts.ACCOUNT_NAME
};
static final int PERSON_ID_COLUMN_INDEX = 0;
static final int NAME_COLUMN_INDEX = 1;
static final int PHONE_TYPE_COLUMN_INDEX = 2;
static final int LABEL_COLUMN_INDEX = 3;
static final int MATCHED_NUMBER_COLUMN_INDEX = 4;
static final int ACCOUNT_NAME_COLUMN_INDEX = 5;
private static final int MENU_ITEM_SELECT_ALL = 1;
private static final int MENU_ITEM_UNSELECT_ALL = 2;
private static final int QUERY_TOKEN = 53;
private static final int UPDATE_TOKEN = 54;
private static final int DIALOG_CONFIRM_DELETE_ALL = 1;
static private Cursor CurrentCursor= null;
private static String CallTypeSelect = null;
static private ArrayList<Long> SelectState;
static private Button DeleteButton = null;
static private Button CancelButton = null;
static private CheckBox SelectAll ;
RecentCallsAdapter mAdapter;
private QueryHandler mQueryHandler;
String mVoiceMailNumber;
static final class ContactInfo {
public long personId;
public String name;
public int type;
public String label;
public String number;
public String formattedNumber;
public static ContactInfo EMPTY = new ContactInfo();
}
public static final class RecentCallsListItemViews {
TextView line1View;
TextView labelView;
TextView numberView;
TextView dateView;
ImageView iconView;
ImageView simView;
ImageView groupIndicator;
TextView groupSize;
CheckBox checkView;
}
static final class CallerInfoQuery {
String number;
int position;
String name;
int numberType;
String numberLabel;
}
/***
* Shared builder used by {@link #formatPhoneNumber(String)} to minimize
* allocations when formatting phone numbers.
*/
private static final SpannableStringBuilder sEditable = new SpannableStringBuilder();
/***
* Invalid formatting type constant for {@link #sFormattingType}.
*/
private static final int FORMATTING_TYPE_INVALID = -1;
/***
* Cached formatting type for current {@link Locale}, as provided by
* {@link PhoneNumberUtils#getFormatTypeForLocale(Locale)}.
*/
private static int sFormattingType = FORMATTING_TYPE_INVALID;
final class CheckboxTag
{
int position;
Long id;
}
/*** Adapter class to fill in data for the Call Log */
final class RecentCallsAdapter extends GroupingListAdapter
implements Runnable, ViewTreeObserver.OnPreDrawListener, View.OnClickListener {
HashMap<String,ContactInfo> mContactInfo;
private final LinkedList<CallerInfoQuery> mRequests;
private volatile boolean mDone;
private boolean mLoading = true;
ViewTreeObserver.OnPreDrawListener mPreDrawListener;
private static final int REDRAW = 1;
private static final int START_THREAD = 2;
private boolean mFirst;
private Thread mCallerIdThread;
private CharSequence[] mLabelArray;
private Drawable mDrawableIncoming;
private Drawable mDrawableOutgoing;
private Drawable mDrawableMissed;
private LayoutInflater mInflater;
/***
* Reusable char array buffers.
*/
private CharArrayBuffer mBuffer1 = new CharArrayBuffer(128);
private CharArrayBuffer mBuffer2 = new CharArrayBuffer(128);
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View viewitem = super.getView(position,convertView,parent);
final RecentCallsListItemViews views = (RecentCallsListItemViews) viewitem.getTag();
if (views != null && views.checkView.getTag() != null){
CheckboxTag tag = (CheckboxTag)views.checkView.getTag() ;
tag.position = position;
}
return viewitem ;
}
public void onClick(View view) {
CheckBox checkView = (CheckBox) view ;
CheckboxTag tag = (CheckboxTag) checkView.getTag() ;
Cursor cursor = (Cursor)mAdapter.getItem(tag.position);
int groupSize = 1;
if (mAdapter.isGroupHeader(tag.position)) {
groupSize = mAdapter.getGroupSize(tag.position);
for (int i = 0; i < groupSize; i++) {
Long id = cursor.getLong(ID_COLUMN_INDEX);
if(checkView.isChecked()){
if(!SelectState.contains(id))
SelectState.add(id);
}else{
if (SelectState.contains(id))
SelectState.remove((Object) id);
}
cursor.moveToNext();
}
mAdapter.notifyDataSetChanged();
}else{
if(checkView.isChecked()){
if(!SelectState.contains(tag.id))
SelectState.add(tag.id);
}else{
if (SelectState.contains(tag.id))
SelectState.remove((Object) tag.id);
}
}
updateCheckboxButtoonViewStatus();
}
public boolean onPreDraw() {
if (mFirst) {
mHandler.sendEmptyMessageDelayed(START_THREAD, 1000);
mFirst = false;
}
return true;
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case REDRAW:
notifyDataSetChanged();
break;
case START_THREAD:
startRequestProcessing();
break;
}
}
};
public RecentCallsAdapter() {
super(MultiSelectCalllogDeleteActivity.this);
mContactInfo = new HashMap<String,ContactInfo>();
mRequests = new LinkedList<CallerInfoQuery>();
mPreDrawListener = null;
mDrawableIncoming = getResources().getDrawable(
R.drawable.ic_call_log_list_incoming_call);
mDrawableOutgoing = getResources().getDrawable(
R.drawable.ic_call_log_list_outgoing_call);
mDrawableMissed = getResources().getDrawable(
R.drawable.ic_call_log_list_missed_call);
SelectState = new ArrayList<Long>();
mLabelArray = getResources().getTextArray(com.android.internal.R.array.phoneTypes);
mInflater = (LayoutInflater) MultiSelectCalllogDeleteActivity.this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/***
* Requery on background thread when {@link Cursor} changes.
*/
@Override
protected void onContentChanged() {
// Start async requery
// startQuery(CallTypeSelect);
}
void setLoading(boolean loading) {
mLoading = loading;
}
@Override
public boolean isEmpty() {
if (mLoading) {
// We don't want the empty state to show when loading.
return false;
} else {
return super.isEmpty();
}
}
public ContactInfo getContactInfo(String number) {
return mContactInfo.get(number);
}
public void startRequestProcessing() {
mDone = false;
mCallerIdThread = new Thread(this);
mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
mCallerIdThread.start();
}
public void stopRequestProcessing() {
mDone = true;
if (mCallerIdThread != null) mCallerIdThread.interrupt();
}
public void clearCache() {
synchronized (mContactInfo) {
mContactInfo.clear();
}
}
private void updateCallLog(CallerInfoQuery ciq, ContactInfo ci) {
// Check if they are different. If not, don't update.
boolean nameNoChange = TextUtils.equals(ciq.name, ci.name);
boolean labelNoChange = TextUtils.equals(ciq.numberLabel, ci.label);
boolean typeNoChange = (ciq.numberType == ci.type);
if (nameNoChange && labelNoChange && typeNoChange) {
return;
} else if (DBG) {
Log.d(TAG,"---------------------update call log-------------------------");
Log.d(TAG, "ContactInfo : name = " + ci.name + ",type = " + ci.type + ",lable = " + ci.label);
Log.d(TAG, "CallerInfoQuery : name = " + ciq.name + ",type = " + ciq.numberType + ",lable = " + ciq.numberLabel);
}
ContentValues values = new ContentValues(3);
values.put(Calls.CACHED_NAME, ci.name);
values.put(Calls.CACHED_NUMBER_TYPE, ci.type);
values.put(Calls.CACHED_NUMBER_LABEL, ci.label);
try {
if(DBG)Log.d(TAG, "Update number=" + ciq.number + " Calls {name="
+ ci.name + ",type= " + ci.type + ",lable=" + ci.label + "}");
MultiSelectCalllogDeleteActivity.this.getContentResolver().update(Calls.CONTENT_URI, values,
Calls.NUMBER + "='" + ciq.number + "'", null);
} catch (SQLiteDiskIOException e) {
Log.w(TAG, "SQLiteDiskIOException while updating call info", e);
} catch (SQLiteFullException e) {
Log.w(TAG, "SQLiteFullException while updating call info", e);
} catch (SQLiteDatabaseCorruptException e) {
Log.w(TAG, "SQLiteDatabaseCorruptException while updating call info", e);
}
}
private void enqueueRequest(String number, int position,
String name, int numberType, String numberLabel) {
CallerInfoQuery ciq = new CallerInfoQuery();
ciq.number = number;
ciq.position = position;
ciq.name = name;
ciq.numberType = numberType;
ciq.numberLabel = numberLabel;
synchronized (mRequests) {
mRequests.add(ciq);
mRequests.notifyAll();
}
}
private boolean queryContactInfo(CallerInfoQuery ciq) {
// First check if there was a prior request for the same number
// that was already satisfied
ContactInfo info = mContactInfo.get(ciq.number);
boolean needNotify = false;
if (info != null && info != ContactInfo.EMPTY) {
if(DBG)Log.d(TAG, "not to query ! update");
// return true;
} else {
if(DBG)Log.d(TAG, "query ! update");
Cursor phonesCursor =
MultiSelectCalllogDeleteActivity.this.getContentResolver().query(
Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
Uri.encode(ciq.number)),
PHONES_PROJECTION, null, null, SIM_INDEX);
if (phonesCursor != null) {
if (phonesCursor.moveToFirst()) {
info = new ContactInfo();
info.personId = phonesCursor.getLong(PERSON_ID_COLUMN_INDEX);
info.name = phonesCursor.getString(NAME_COLUMN_INDEX);
info.type = phonesCursor.getInt(PHONE_TYPE_COLUMN_INDEX);
info.label = phonesCursor.getString(LABEL_COLUMN_INDEX);
info.number = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
boolean isSimNumber = ContactsUtils.isSimNumber(MultiSelectCalllogDeleteActivity.this, info.personId) > 0 ? true : false;
if(isSimNumber){
info.type = 0;
}
info.number = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
String accountName = phonesCursor.getString(ACCOUNT_NAME_COLUMN_INDEX);
if (Account.SIM1_ACCOUNT_NAME.equals(accountName) || "SIM".equals(accountName)) {
if(Config.isMSMS){
info.label = "SIM1";
} else {
info.label = "SIM";
}
} else if (Account.SIM2_ACCOUNT_NAME.equals(accountName)) {
info.label = "SIM2";
} else {
info.label = phonesCursor.getString(LABEL_COLUMN_INDEX);
info.type = phonesCursor.getInt(PHONE_TYPE_COLUMN_INDEX);
}
info.number = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
// New incoming phone number invalidates our formatted
// cache. Any cache fills happen only on the GUI thread.
info.formattedNumber = null;
mContactInfo.put(info.number, info);
// Inform list to update this item, if in view
needNotify = true;
}
phonesCursor.close();
}
}
if (info != null) {
updateCallLog(ciq, info);
}
return needNotify;
}
/**
* Handles requests for contact name and number type
* @see java.lang.Runnable#run()
*/
public void run() {
boolean needNotify = false;
while (!mDone) {
CallerInfoQuery ciq = null;
synchronized (mRequests) {
if (!mRequests.isEmpty()) {
ciq = mRequests.removeFirst();
} else {
if (needNotify) {
needNotify = false;
mHandler.sendEmptyMessage(REDRAW);
}
try {
mRequests.wait(1000);
} catch (InterruptedException ie) {
// Ignore and continue processing requests
}
}
}
if (ciq != null && queryContactInfo(ciq)) {
needNotify = true;
}
}
}
@Override
protected void addGroups(Cursor cursor) {
int count = cursor.getCount();
if (count == 0 || !cursor.moveToFirst()) {
return;
}
int groupItemCount = 1;
CharArrayBuffer currentValue = mBuffer1;
CharArrayBuffer value = mBuffer2;
cursor.copyStringToBuffer(NUMBER_COLUMN_INDEX, currentValue);
int currentCallType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
for (int i = 1; i < count; i++) {
cursor.moveToNext();
cursor.copyStringToBuffer(NUMBER_COLUMN_INDEX, value);
boolean sameNumber = equalPhoneNumbers(value, currentValue);
// Group adjacent calls with the same number. Make an exception
// for the latest item if it was a missed call. We don't want
// a missed call to be hidden inside a group.
if (sameNumber && currentCallType != Calls.MISSED_TYPE) {
groupItemCount++;
} else {
if (groupItemCount > 1) {
addGroup(i - groupItemCount, groupItemCount, false);
}
groupItemCount = 1;
// Swap buffers
CharArrayBuffer temp = currentValue;
currentValue = value;
value = temp;
// If we have just examined a row following a missed call, make
// sure that it is grouped with subsequent calls from the same number
// even if it was also missed.
if (sameNumber && currentCallType == Calls.MISSED_TYPE) {
currentCallType = 0; // "not a missed call"
} else {
currentCallType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
}
}
}
if (groupItemCount > 1) {
addGroup(count - groupItemCount, groupItemCount, false);
}
}
protected boolean equalPhoneNumbers(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
// TODO add PhoneNumberUtils.compare(CharSequence, CharSequence) to avoid
// string allocation
return PhoneNumberUtils.compare(new String(buffer1.data, 0, buffer1.sizeCopied),
new String(buffer2.data, 0, buffer2.sizeCopied));
}
@Override
protected View newStandAloneView(Context context, ViewGroup parent) {
View view = mInflater.inflate(R.layout.multi_select_calllog_list_item, parent, false);
findAndCacheViews(view);
return view;
}
@Override
protected void bindStandAloneView(View view, Context context, Cursor cursor) {
bindView(context, view, cursor);
}
@Override
protected View newChildView(Context context, ViewGroup parent) {
View view = mInflater.inflate(R.layout.multi_select_calllog_list_item, parent, false);
findAndCacheViews(view);
return view;
}
@Override
protected void bindChildView(View view, Context context, Cursor cursor) {
bindView(context, view, cursor);
}
@Override
protected View newGroupView(Context context, ViewGroup parent) {
View view = mInflater.inflate(R.layout.multi_select_calllog_list_group_item, parent, false);
findAndCacheViews(view);
return view;
}
@Override
protected void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
boolean expanded) {
final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
int groupIndicator = expanded
? com.android.internal.R.drawable.expander_ic_maximized
: com.android.internal.R.drawable.expander_ic_minimized;
views.groupIndicator.setImageResource(groupIndicator);
views.groupSize.setText("(" + groupSize + ")");
bindView(context, view, cursor);
}
private void findAndCacheViews(View view) {
// Get the views to bind to
RecentCallsListItemViews views = new RecentCallsListItemViews();
views.line1View = (TextView) view.findViewById(R.id.line1);
views.labelView = (TextView) view.findViewById(R.id.label);
views.numberView = (TextView) view.findViewById(R.id.number);
views.dateView = (TextView) view.findViewById(R.id.date);
views.simView = (ImageView) view.findViewById(R.id.sim);
views.iconView = (ImageView) view.findViewById(R.id.call_type_icon);
views.groupIndicator = (ImageView) view.findViewById(R.id.groupIndicator);
views.groupSize = (TextView) view.findViewById(R.id.groupSize);
views.checkView = (CheckBox) view.findViewById(R.id.checkbox_select);
views.checkView.setOnClickListener(this);
view.setTag(views);
}
public void bindView(Context context, View view, Cursor c) {
final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
String number = c.getString(NUMBER_COLUMN_INDEX);
String formattedNumber = null;
String callerName = c.getString(CALLER_NAME_COLUMN_INDEX);
int callerNumberType = c.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
String callerNumberLabel = c.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
// Store away the number so we can call it directly if you click on the call icon
// Lookup contacts with this number
ContactInfo info = mContactInfo.get(number);
if (info == null) {
// Mark it as empty and queue up a request to find the name
// The db request should happen on a non-UI thread
info = ContactInfo.EMPTY;
mContactInfo.put(number, info);
enqueueRequest(number, c.getPosition(),
callerName, callerNumberType, callerNumberLabel);
} else if (info != ContactInfo.EMPTY) { // Has been queried
// Check if any data is different from the data cached in the
// calls db. If so, queue the request so that we can update
// the calls db.
boolean nameChange = !TextUtils.equals(info.name, callerName);
boolean typeChange = info.type != callerNumberType;
boolean lableChange = !TextUtils.equals(info.label, callerNumberLabel);
Log.d(TAG, "nameChange = " + nameChange + ",typeChange = " + typeChange + ",lableChange = " + lableChange);
if (nameChange || typeChange || lableChange) {
if (DBG)Log.d(TAG,"---------request update call log---------");
if (DBG)Log.d(TAG, "ContactInfo : name = " + info.name + ",type = " + info.type + ",lable = " + info.label);
if (DBG)Log.d(TAG, "Databases : name = " + callerName + ",type = " + callerNumberType + ",lable = " + callerNumberLabel);
// Something is amiss, so sync up.
enqueueRequest(number, c.getPosition(),
callerName, callerNumberType, callerNumberLabel);
}
// Format and cache phone number for found contact
if (info.formattedNumber == null) {
info.formattedNumber = formatPhoneNumber(info.number);
}
formattedNumber = info.formattedNumber;
}
String name = info.name;
int ntype = info.type;
String label = info.label;
// If there's no name cached in our hashmap, but there's one in the
// calls db, use the one in the calls db. Otherwise the name in our
// hashmap is more recent, so it has precedence.
if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(callerName)) {
name = callerName;
ntype = callerNumberType;
label = callerNumberLabel;
// Format the cached call_log phone number
formattedNumber = formatPhoneNumber(number);
}
// Set the text lines and call icon.
// Assumes the call back feature is on most of the
// time. For private and unknown numbers: hide it.
//views.callView.setVisibility(View.VISIBLE);
if (!TextUtils.isEmpty(name)) {
views.line1View.setText(name);
views.labelView.setVisibility(View.VISIBLE);
CharSequence numberLabel = Phone.getDisplayLabel(context, ntype, label,
mLabelArray);
views.numberView.setVisibility(View.VISIBLE);
views.numberView.setText(formattedNumber);
if (!TextUtils.isEmpty(numberLabel)) {
views.labelView.setText(numberLabel);
views.labelView.setVisibility(View.VISIBLE);
} else {
views.labelView.setVisibility(View.GONE);
}
} else {
if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
number = getString(R.string.unknown);
// views.callView.setVisibility(View.INVISIBLE);
} else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
number = getString(R.string.private_num);
//views.callView.setVisibility(View.INVISIBLE);
} else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
number = getString(R.string.payphone);
} else if (number.equals(mVoiceMailNumber)) {
number = getString(R.string.voicemail);
} else {
// Just a raw number, and no cache, so format it nicely
number = formatPhoneNumber(number);
}
views.line1View.setText(number);
views.numberView.setVisibility(View.GONE);
views.labelView.setVisibility(View.GONE);
}
long date = c.getLong(DATE_COLUMN_INDEX);
// Set the date/time field by mixing relative and absolute times.
int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
views.dateView.setText(DateUtils.getRelativeTimeSpanString(date,
System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, flags));
if (views.iconView != null) {
int sim = c.getInt(SIM_COLUMN_INDEX);
if (Config.isMSMS && !PhoneNumberUtils.isEmergencyNumber(number)) {
views.simView.setVisibility(View.VISIBLE);
if (0 == sim) {
views.simView.setImageResource(R.drawable.ico_list_sim1);
} else if (1 == sim) {
views.simView.setImageResource(R.drawable.ico_list_sim2);
}
} else {
views.simView.setVisibility(View.GONE);
}
int type = c.getInt(CALL_TYPE_COLUMN_INDEX);
// Set the icon
switch (type) {
case Calls.INCOMING_TYPE:
views.iconView.setImageDrawable(mDrawableIncoming);
break;
case Calls.OUTGOING_TYPE:
views.iconView.setImageDrawable(mDrawableOutgoing);
break;
case Calls.MISSED_TYPE:
views.iconView.setImageDrawable(mDrawableMissed);
break;
}
}
if (views.checkView != null) {
views.checkView.setFocusableInTouchMode(false);
views.checkView.setFocusable(false);
if (SelectState.contains(c.getLong(ID_COLUMN_INDEX))) {
views.checkView.setChecked(true);
} else {
views.checkView.setChecked(false);
}
CheckboxTag tags = new CheckboxTag();
tags.id = c.getLong(ID_COLUMN_INDEX);
views.checkView.setTag(tags);
// views.checkView.setTag(c.getString(ID_COLUMN_INDEX));
}
// Listen for the first draw
if (mPreDrawListener == null) {
mFirst = true;
mPreDrawListener = this;
view.getViewTreeObserver().addOnPreDrawListener(this);
}
}
}
private static final class QueryHandler extends AsyncQueryHandler {
private final WeakReference<MultiSelectCalllogDeleteActivity> mActivity;
/***
* Simple handler that wraps background calls to catch
* {@link SQLiteException}, such as when the disk is full.
*/
protected class CatchingWorkerHandler extends AsyncQueryHandler.WorkerHandler {
public CatchingWorkerHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
try {
// Perform same query while catching any exceptions
super.handleMessage(msg);
} catch (SQLiteDiskIOException e) {
Log.w(TAG, "SQLiteDiskIOException on background worker thread", e);
} catch (SQLiteFullException e) {
Log.w(TAG, "SQLiteFullException on background worker thread", e);
} catch (SQLiteDatabaseCorruptException e) {
Log.w(TAG, "SQLiteDatabaseCorruptException on background worker thread", e);
}
}
}
@Override
protected Handler createHandler(Looper looper) {
// Provide our special handler that catches exceptions
return new CatchingWorkerHandler(looper);
}
public QueryHandler(Context context) {
super(context.getContentResolver());
mActivity = new WeakReference<MultiSelectCalllogDeleteActivity>(
(MultiSelectCalllogDeleteActivity) context);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
final MultiSelectCalllogDeleteActivity activity = mActivity.get();
if (activity != null && !activity.isFinishing()) {
final MultiSelectCalllogDeleteActivity.RecentCallsAdapter callsAdapter = activity.mAdapter;
callsAdapter.setLoading(false);
Log.i(TAG, "onQueryComplete changeCursor");
callsAdapter.changeCursor(cursor);
if (CurrentCursor != null) {
CurrentCursor.close();
}
CurrentCursor = cursor;
updateCheckboxButtoonViewStatus();
} else {
cursor.close();
}
}
}
class CancelButtonListener implements OnClickListener{
@Override
public void onClick(View v) {
finish();
}
}
class DeleteButtonListener implements OnClickListener{
@Override
public void onClick(View v) {
if (SelectState.isEmpty()) {
Toast.makeText(MultiSelectCalllogDeleteActivity.this, getString(R.string.select_one),
Toast.LENGTH_LONG).show();
} else {
showDialog(DIALOG_CONFIRM_DELETE_ALL);
}
}
}
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.multi_select_calllog);
// Typing here goes to the dialer
setDefaultKeyMode(DEFAULT_KEYS_DIALER);
mAdapter = new RecentCallsAdapter();
getListView().setOnCreateContextMenuListener(this);
setListAdapter(mAdapter);
SelectAll = (CheckBox)findViewById(R.id.checkbox_selected_all);
SelectAll.setFocusableInTouchMode(false);
SelectAll.setFocusable(false);
SelectAll.setOnClickListener(this);
DeleteButton = (Button)findViewById(R.id.DeleteButton);
CancelButton = (Button)findViewById(R.id.CancelButton);
DeleteButton.setOnClickListener(new DeleteButtonListener());
CancelButton.setOnClickListener(new CancelButtonListener());
mQueryHandler = new QueryHandler(this);
// Reset locale-based formatting cache
sFormattingType = FORMATTING_TYPE_INVALID;
Intent intent = getIntent();
CallTypeSelect = intent.getStringExtra("CALLTYPE");
Log.i(TAG, "onCreate");
}
@Override
protected void onResume() {
// The adapter caches looked up numbers, clear it so they will get
// looked up again.
if (mAdapter != null) {
mAdapter.clearCache();
}
startQuery(CallTypeSelect);
resetNewCallsFlag();
super.onResume();
mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
cancelMissedCallsNotification();
}
@Override
protected void onPause() {
super.onPause();
// Kill the requests thread
mAdapter.stopRequestProcessing();
}
@Override
protected void onDestroy() {
super.onDestroy();
mAdapter.stopRequestProcessing();
if (CurrentCursor != null && !CurrentCursor.isClosed()) {
CurrentCursor.close();
}
if (mAdapter != null && mAdapter.getCursor() != null && !mAdapter.getCursor().isClosed()) {
mAdapter.getCursor().close();
}
mAdapter.changeCursor(null);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// Clear notifications only when window gains focus. This activity won't
// immediately receive focus if the keyguard screen is above it.
// if (hasFocus) {
// try {
// ITelephony iTelephony =
// ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
// if (iTelephony != null) {
// iTelephony.cancelMissedCallsNotification();
// } else {
// Log.w(TAG, "Telephony service is null, can't call " +
// "cancelMissedCallsNotification");
// }
// } catch (RemoteException e) {
// Log.e(TAG,
// "Failed to clear missed calls notification due to remote exception");
// }
// }
}
/***
* Format the given phone number using
* {@link PhoneNumberUtils#formatNumber(android.text.Editable, int)}. This
* helper method uses {@link #sEditable} and {@link #sFormattingType} to
* prevent allocations between multiple calls.
* <p>
* Because of the shared {@link #sEditable} builder, <b>this method is not
* thread safe</b>, and should only be called from the GUI thread.
* <p>
* If the given String object is null or empty, return an empty String.
*/
private String formatPhoneNumber(String number) {
if (TextUtils.isEmpty(number)) {
return "";
}
// Cache formatting type if not already present
if (sFormattingType == FORMATTING_TYPE_INVALID) {
sFormattingType = PhoneNumberUtils.getFormatTypeForLocale(Locale.getDefault());
}
sEditable.clear();
sEditable.append(number);
PhoneNumberUtils.formatNumber(sEditable, sFormattingType);
return sEditable.toString();
}
private void resetNewCallsFlag() {
// Mark all "new" missed calls as not new anymore
StringBuilder where = new StringBuilder("type=");
where.append(Calls.MISSED_TYPE);
where.append(" AND new=1");
ContentValues values = new ContentValues(1);
values.put(Calls.NEW, "0");
mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI,
values, where.toString(), null);
}
private void startQuery() {
mAdapter.setLoading(true);
// Cancel any pending queries
mQueryHandler.cancelOperation(QUERY_TOKEN);
mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI,
CALL_LOG_PROJECTION, null, null, Calls.DEFAULT_SORT_ORDER);
}
private void startQuery(String CallTypeSelect) {
mAdapter.setLoading(true);
Log.w(TAG, "startQuery CallTypeSelect: " + CallTypeSelect);
// Cancel any pending queries
mQueryHandler.cancelOperation(QUERY_TOKEN);
mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI,
CALL_LOG_PROJECTION, CallTypeSelect, null, Calls.DEFAULT_SORT_ORDER);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, MENU_ITEM_SELECT_ALL, 0, R.string.recent_Call_log_checkbox_selecte_all)
.setIcon(R.drawable.ic_menu_mark);
menu.add(0, MENU_ITEM_UNSELECT_ALL, 0, R.string.recentCalls_button_Cancel)
.setIcon(android.R.drawable.ic_menu_revert);
return true;
}
@Override
protected Dialog onCreateDialog(int id, Bundle args) {
switch (id) {
case DIALOG_CONFIRM_DELETE_ALL:
return new AlertDialog.Builder(this)
.setTitle(R.string.clearCallLogConfirmation_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.clearCallLogConfirmation)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, new android.content.DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
multiSelectCalllogDelete();
}
})
.setCancelable(false)
.create();
}
return null;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ITEM_SELECT_ALL: {
multiSelectCalllogSelectAll();
break;
}
case MENU_ITEM_UNSELECT_ALL: {
multiSelectCalllogUnSelectAll();
break;
}
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Log.i(TAG, "onListItemClick: " + position);
if (mAdapter.isGroupHeader(position)) {
mAdapter.toggleGroup(position);
Log.i(TAG, "onListItemClick Adapter.toggleGroup: " + position);
} else {
if (!(v.getTag() instanceof RecentCallsListItemViews)) {
Log.e(TAG, "Unexpected bound view: " + v);
return;
}
RecentCallsListItemViews itemviews = (RecentCallsListItemViews)v.getTag();
CheckboxTag column_id= (CheckboxTag) itemviews.checkView.getTag();
Log.w(TAG, "onListItemClick: " + column_id.id);
Long p= column_id.id;
if(SelectState.contains(p))
{
SelectState.remove((Object) p);
itemviews.checkView.setChecked(false);
}
else
{
SelectState.add(p);
itemviews.checkView.setChecked(true);
}
updateCheckboxButtoonViewStatus();
}
}
@Override
public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
boolean globalSearch) {
if (globalSearch) {
super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
} else {
ContactsSearchManager.startSearch(this, initialQuery);
}
}
private void multiSelectCalllogDelete() {
int count = SelectState.size();
StringBuilder sb = new StringBuilder();
// RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
for (int i = 0; i < count; i++) {
if (i != 0) {
sb.append(",");
}
long id = SelectState.get(i);
sb.append(id);
}
Log.i(TAG, "multiSelectCalllogDelete: " + sb);
getContentResolver().delete(Calls.CONTENT_URI, Calls._ID + " IN (" + sb + ")",null);
finish();
}
private void multiSelectCalllogSelectAll() {
SelectState.clear();
if (CurrentCursor != null){
try {
if (CurrentCursor.moveToFirst()) {
do {
Long p = CurrentCursor.getLong(ID_COLUMN_INDEX);
if(!SelectState.contains(p))
SelectState.add(p);
}while (CurrentCursor.moveToNext());
}
} catch (Exception e) {
Log.i(TAG, "Exception");
}
if (SelectState.size() > 0) {
DeleteButton.setEnabled(true);
}
mAdapter.notifyDataSetChanged();
updateCheckboxButtoonViewStatus();
}
}
private void multiSelectCalllogUnSelectAll() {
SelectState.clear();
mAdapter.notifyDataSetChanged();
updateCheckboxButtoonViewStatus();
}
public static void updateCheckboxButtoonViewStatus() {
if(SelectState.size()== CurrentCursor.getCount()) {
if(!SelectAll.isChecked())
SelectAll.setChecked(true);
DeleteButton.setEnabled(true);
} else if(SelectState.size()>0) {
DeleteButton.setEnabled(true);
if(SelectAll.isChecked())
SelectAll.setChecked(false);
} else {
DeleteButton.setEnabled(false);
if(SelectAll.isChecked())
SelectAll.setChecked(false);
}
}
public void onClick(View view) {
CheckBox checkView = (CheckBox) view ;
if(checkView.isChecked()) {
multiSelectCalllogSelectAll();
} else {
multiSelectCalllogUnSelectAll();
}
}
// clear statusbar missed calls notification
private void cancelMissedCallsNotification() {
try {
ITelephony iTelephony = ITelephony.Stub.asInterface(ServiceManager
.getService("phone"));
if (iTelephony != null) {
iTelephony.cancelMissedCallsNotification();
} else {
Log.w(TAG, "Telephony service is null, can't call "
+ "cancelMissedCallsNotification");
}
} catch (RemoteException e) {
Log.e(TAG,
"Failed to clear missed calls notification due to remote exception");
}
}
}