/* * Copyright (C) 2013 The CyanogenMod 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 dev.dworks.apps.asecure.blacklist; import java.util.HashMap; import android.app.ActionBar; import android.app.Activity; import android.app.FragmentTransaction; import android.app.ListFragment; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Process; import android.provider.ContactsContract.PhoneLookup; import android.text.TextUtils; import android.util.SparseArray; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.ListView; import android.widget.ResourceCursorAdapter; import android.widget.Switch; import android.widget.TextView; import dev.dworks.apps.asecure.R; import dev.dworks.apps.asecure.blacklist.Telephony.Blacklist; import dev.dworks.apps.asecure.misc.Utils; /** * Blacklist settings UI for the Phone app. */ public class BlacklistSettings extends ListFragment implements CompoundButton.OnCheckedChangeListener { private static final String[] BLACKLIST_PROJECTION = { Blacklist._ID, Blacklist.NUMBER, Blacklist.PHONE_MODE, Blacklist.MESSAGE_MODE }; private static final int COLUMN_ID = 0; private static final int COLUMN_NUMBER = 1; private static final int COLUMN_PHONE = 2; private static final int COLUMN_MESSAGE = 3; private Switch mEnabledSwitch; private boolean mLastEnabledState; private BlacklistAdapter mAdapter; private Cursor mCursor; private TextView mEmptyView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.preference_list_fragment, container, false); } @Override public void onActivityCreated(Bundle icicle) { super.onActivityCreated(icicle); setHasOptionsMenu(true); final Activity activity = getActivity(); mEnabledSwitch = new Switch(activity); final int padding = activity.getResources().getDimensionPixelSize( R.dimen.action_bar_switch_padding); mEnabledSwitch.setPaddingRelative(0, 0, padding, 0); mEnabledSwitch.setOnCheckedChangeListener(this); mCursor = getActivity().managedQuery(Blacklist.CONTENT_URI, BLACKLIST_PROJECTION, null, null, null); mAdapter = new BlacklistAdapter(getActivity(), null); mEmptyView = (TextView) getView().findViewById(android.R.id.empty); final ListView listView = getListView(); listView.setAdapter(mAdapter); listView.setEmptyView(mEmptyView); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.blacklist, menu); } @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); menu.findItem(R.id.blacklist_add).setEnabled(mLastEnabledState); menu.findItem(R.id.blacklist_prefs).setEnabled(mLastEnabledState); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.blacklist_add: showEntryEditDialog(-1); return true; case R.id.blacklist_prefs: PreferenceFragment prefs = new PreferenceFragment(); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.replace(android.R.id.content, prefs); ft.addToBackStack(null); ft.commitAllowingStateLoss(); return true; } return super.onOptionsItemSelected(item); } @Override public void onStart() { super.onStart(); final Activity activity = getActivity(); activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, ActionBar.DISPLAY_SHOW_CUSTOM); activity.getActionBar().setCustomView(mEnabledSwitch, new ActionBar.LayoutParams( ActionBar.LayoutParams.WRAP_CONTENT, ActionBar.LayoutParams.WRAP_CONTENT, Gravity.CENTER_VERTICAL | Gravity.END)); } @Override public void onStop() { super.onStop(); final Activity activity = getActivity(); activity.getActionBar().setDisplayOptions(0, ActionBar.DISPLAY_SHOW_CUSTOM); activity.getActionBar().setCustomView(null); } @Override public void onResume() { super.onResume(); final Context context = getActivity(); mLastEnabledState = BlacklistUtils.isBlacklistEnabled(context); mEnabledSwitch.setChecked(mLastEnabledState); updateEnabledState(); } @Override public void onListItemClick(ListView l, View v, int position, long id) { showEntryEditDialog(id); } private void showEntryEditDialog(long id) { EntryEditDialogFragment fragment = EntryEditDialogFragment.newInstance(id); fragment.show(getFragmentManager(), "blacklist_edit"); } private void updateEnabledState() { getListView().setEnabled(mLastEnabledState); getActivity().invalidateOptionsMenu(); mEmptyView.setText(mLastEnabledState ? R.string.blacklist_empty_text : R.string.blacklist_disabled_empty_text); mAdapter.swapCursor(mLastEnabledState ? mCursor : null); } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (buttonView == mEnabledSwitch && isChecked != mLastEnabledState) { Settings.System.putInt(getActivity().getContentResolver(), Settings.System.PHONE_BLACKLIST_ENABLED, isChecked ? 1 : 0); mLastEnabledState = isChecked; updateEnabledState(); } } private static class BlacklistAdapter extends ResourceCursorAdapter implements ToggleImageView.OnCheckedChangeListener { private Object mLock = new Object(); private ContentResolver mResolver; private String mCurrentCountryIso; private SparseArray<String> mRequestedLookups = new SparseArray<String>(); private HashMap<String, String> mContactNameCache = new HashMap<String, String>(); private Handler mMainHandler = new Handler() { @Override public void handleMessage(Message msg) { int lookupIndex = msg.arg1; String name = (String) msg.obj; mContactNameCache.put(mRequestedLookups.get(lookupIndex), name == null ? "" : name); mRequestedLookups.delete(lookupIndex); notifyDataSetChanged(); } }; private Handler mQueryHandler; private class QueryHandler extends Handler { public static final int MSG_LOOKUP = 1; private static final int MSG_FINISH = 2; public QueryHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_LOOKUP: String name = lookupNameForNumber((String) msg.obj); mMainHandler.obtainMessage(0, msg.arg1, 0, name).sendToTarget(); synchronized (mLock) { if (mQueryHandler != null) { Message finishMessage = mQueryHandler.obtainMessage(MSG_FINISH); mQueryHandler.sendMessageDelayed(finishMessage, 3000); } } break; case MSG_FINISH: synchronized (mLock) { if (mQueryHandler != null) { mQueryHandler.getLooper().quit(); mQueryHandler = null; } } break; } } private String lookupNameForNumber(String number) { if (!TextUtils.isEmpty(mCurrentCountryIso)) { // Normalise the number: this is needed because the PhoneLookup query // below does not accept a country code as an input. String numberE164 = Utils.getFormattedPhoneNumber(number, mCurrentCountryIso); if (!TextUtils.isEmpty(numberE164)) { // Only use it if the number could be formatted to E164. number = numberE164; } } String result = null; final String[] projection = new String[] { PhoneLookup.DISPLAY_NAME }; Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); Cursor cursor = mResolver.query(uri, projection, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { result = cursor.getString(0); } cursor.close(); } return result; } } public BlacklistAdapter(Context context, Cursor cursor) { super(context, R.layout.blacklist_entry_row, cursor); /* final CountryDetector detector = (CountryDetector) context.getSystemService(Context.COUNTRY_DETECTOR); mCurrentCountryIso = detector.detectCountry().getCountryIso(); mResolver = context.getContentResolver();*/ } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { View view = super.newView(context, cursor, parent); ViewHolder holder = new ViewHolder(); holder.mainText = (TextView) view.findViewById(R.id.number); holder.subText = (TextView) view.findViewById(R.id.name); holder.callStatus = (ToggleImageView) view.findViewById(R.id.block_calls); holder.messageStatus = (ToggleImageView) view.findViewById(R.id.block_messages); holder.callStatus.setTag(Blacklist.PHONE_MODE); holder.callStatus.setOnCheckedChangeListener(this); holder.messageStatus.setTag(Blacklist.MESSAGE_MODE); holder.messageStatus.setOnCheckedChangeListener(this); view.setTag(holder); return view; } @Override public void bindView(View view, Context context, Cursor cursor) { ViewHolder holder = (ViewHolder) view.getTag(); String number = cursor.getString(COLUMN_NUMBER); String name = mContactNameCache.get(number); String formattedNumber = Utils.getFormattedPhoneNumber(number, mCurrentCountryIso); if (TextUtils.isEmpty(name)) { holder.mainText.setText(formattedNumber); holder.subText.setVisibility(View.GONE); } else { holder.mainText.setText(name); holder.subText.setText(formattedNumber); holder.subText.setVisibility(View.VISIBLE); } if (name == null) { int id = cursor.getInt(COLUMN_ID); scheduleNameLookup(id, number); } holder.callStatus.setCheckedInternal(cursor.getInt(COLUMN_PHONE) != 0, false); holder.messageStatus.setCheckedInternal(cursor.getInt(COLUMN_MESSAGE) != 0, false); holder.position = cursor.getPosition(); } @Override public void onCheckedChanged(ToggleImageView view, boolean isChecked) { View parent = (View) view.getParent(); ViewHolder holder = (ViewHolder) parent.getTag(); String column = (String) view.getTag(); long id = getItemId(holder.position); Uri uri = ContentUris.withAppendedId(Blacklist.CONTENT_URI, id); ContentValues cv = new ContentValues(); cv.put(column, view.isChecked() ? 1 : 0); if (mResolver.update(uri, cv, null, null) <= 0) { // something went wrong, force an update to the correct state notifyDataSetChanged(); } } private void scheduleNameLookup(int id, String number) { synchronized (mLock) { if (mQueryHandler == null) { HandlerThread thread = new HandlerThread("blacklist_contact_query", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mQueryHandler = new QueryHandler(thread.getLooper()); } } mRequestedLookups.put(id, number); Message msg = mQueryHandler.obtainMessage(QueryHandler.MSG_LOOKUP, id, 0, number); msg.sendToTarget(); } private static class ViewHolder { TextView mainText; TextView subText; ToggleImageView callStatus; ToggleImageView messageStatus; int position; } } }