summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk8
-rw-r--r--AndroidManifest.xml5
-rw-r--r--res/drawable/ic_add.xml19
-rw-r--r--res/drawable/ic_close.xml9
-rw-r--r--res/layout/expanding_entry_card_item.xml56
-rw-r--r--res/layout/people_activity.xml12
-rw-r--r--res/values/cm_strings.xml5
-rw-r--r--src/com/android/contacts/ContactsApplication.java4
-rw-r--r--src/com/android/contacts/StartCallResultReceiver.java42
-rw-r--r--src/com/android/contacts/activities/ActionBarAdapter.java39
-rw-r--r--src/com/android/contacts/activities/ContactSelectionActivity.java5
-rw-r--r--src/com/android/contacts/activities/MultiPickContactActivity.java3
-rw-r--r--src/com/android/contacts/activities/PeopleActivity.java541
-rw-r--r--src/com/android/contacts/group/GroupDetailFragment.java2
-rw-r--r--src/com/android/contacts/incall/InCallPluginHelper.java111
-rw-r--r--src/com/android/contacts/incall/InCallPluginInfo.java102
-rw-r--r--src/com/android/contacts/incall/InCallPluginUtils.java201
-rw-r--r--src/com/android/contacts/interactions/CallLogInteraction.java62
-rw-r--r--src/com/android/contacts/interactions/CallLogInteractionsLoader.java68
-rw-r--r--src/com/android/contacts/list/ContactTileListFragment.java2
-rw-r--r--src/com/android/contacts/list/PluginContactBrowseListFragment.java765
-rw-r--r--src/com/android/contacts/quickcontact/ExpandingEntryCardView.java162
-rw-r--r--src/com/android/contacts/quickcontact/QuickContactActivity.java563
23 files changed, 2528 insertions, 258 deletions
diff --git a/Android.mk b/Android.mk
index b9bcf90c3..3d19b64c1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -6,7 +6,10 @@ LOCAL_MODULE_TAGS := optional
contacts_common_dir := ../ContactsCommon
phone_common_dir := ../PhoneCommon
-src_dirs := src $(contacts_common_dir)/src $(phone_common_dir)/src
+src_dirs := src $(contacts_common_dir)/src \
+ $(phone_common_dir)/src \
+ $(phone_common_dir)/src-ambient
+
res_dirs := res $(contacts_common_dir)/res $(phone_common_dir)/res
LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
@@ -17,7 +20,8 @@ LOCAL_AAPT_FLAGS := \
--auto-add-overlay \
--extra-packages com.android.contacts.common \
--extra-packages com.android.phone.common \
- --extra-packages android.support.v7.cardview
+ --extra-packages android.support.v7.cardview \
+ --extra-packages com.cyanogen.ambient
LOCAL_JAVA_LIBRARIES := telephony-common voip-common
LOCAL_STATIC_JAVA_LIBRARIES := \
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 319857c04..c5c4a5dbe 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -49,6 +49,11 @@
<uses-permission android:name="android.permission.READ_PHONE_BLACKLIST" />
<uses-permission android:name="android.permission.CHANGE_PHONE_BLACKLIST" />
+ <!-- Receive plugin status changes -->
+ <uses-permission android:name="com.cyanogen.ambient.permission.PLUGIN_STATUS_CHANGED" />
+ <!-- Connect to AmbientCore to use InCall Plugins -->
+ <uses-permission android:name="com.cyanogen.ambient.permission.BIND_INCALL_SERVICE" />
+
<application
android:name="com.android.contacts.ContactsApplication"
android:label="@string/applicationLabel"
diff --git a/res/drawable/ic_add.xml b/res/drawable/ic_add.xml
new file mode 100644
index 000000000..2d64346f8
--- /dev/null
+++ b/res/drawable/ic_add.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="15dp"
+ android:height="14dp"
+ android:viewportWidth="15"
+ android:viewportHeight="14">
+
+ <group
+ android:translateX="-210.000000"
+ android:translateY="-762.000000">
+ <group
+ android:translateX="210.500000"
+ android:translateY="762.000000">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M14,6 L8,6 L8,0 L6,0 L6,6 L0,6 L0,8 L6,8 L6,14 L8,14 L8,8 L14,8 L14,6 L14,6 Z" />
+ </group>
+ </group>
+</vector> \ No newline at end of file
diff --git a/res/drawable/ic_close.xml b/res/drawable/ic_close.xml
new file mode 100644
index 000000000..fdbfc4287
--- /dev/null
+++ b/res/drawable/ic_close.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"
+ android:fillColor="#9e9e9e"/>
+</vector>
diff --git a/res/layout/expanding_entry_card_item.xml b/res/layout/expanding_entry_card_item.xml
index 20e90ebf9..17239d39b 100644
--- a/res/layout/expanding_entry_card_item.xml
+++ b/res/layout/expanding_entry_card_item.xml
@@ -85,28 +85,38 @@
android:layout_marginTop="@dimen/expanding_entry_card_item_text_icon_margin_top"
android:layout_marginEnd="@dimen/expanding_entry_card_item_text_icon_margin_right" />
- <ImageView
- android:id="@+id/icon_alternate"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_toStartOf="@+id/third_icon"
- android:layout_alignWithParentIfMissing="true"
- android:visibility="gone"
- android:background="?android:attr/selectableItemBackgroundBorderless"
- android:paddingTop="@dimen/expanding_entry_card_item_icon_margin_top"
- android:paddingBottom="@dimen/expanding_entry_card_item_alternate_icon_margin_bottom"
- android:layout_marginStart="@dimen/expanding_entry_card_item_alternate_icon_start_margin" />
+ <ImageView
+ android:id="@+id/icon_alternate"
+ android:layout_width="@dimen/expanding_entry_card_item_icon_width"
+ android:layout_height="@dimen/expanding_entry_card_item_icon_height"
+ android:layout_alignParentTop="true"
+ android:layout_toStartOf="@+id/third_icon"
+ android:layout_alignWithParentIfMissing="true"
+ android:visibility="gone"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:layout_marginTop="@dimen/expanding_entry_card_item_icon_margin_top"
+ android:layout_marginBottom="@dimen/expanding_entry_card_item_alternate_icon_margin_bottom"
+ android:layout_marginStart="@dimen/expanding_entry_card_item_alternate_icon_start_margin" />
+
+ <ImageView
+ android:id="@+id/third_icon"
+ android:layout_width="@dimen/expanding_entry_card_item_icon_width"
+ android:layout_height="@dimen/expanding_entry_card_item_icon_height"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentTop="true"
+ android:visibility="gone"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:layout_marginTop="@dimen/expanding_entry_card_item_icon_margin_top"
+ android:layout_marginBottom="@dimen/expanding_entry_card_item_alternate_icon_margin_bottom"
+ android:layout_marginStart="@dimen/expanding_entry_card_item_alternate_icon_start_margin" />
- <ImageView
- android:id="@+id/third_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_alignParentTop="true"
- android:visibility="gone"
- android:background="?android:attr/selectableItemBackgroundBorderless"
- android:paddingTop="@dimen/expanding_entry_card_item_icon_margin_top"
- android:paddingBottom="@dimen/expanding_entry_card_item_alternate_icon_margin_bottom"
- android:layout_marginStart="@dimen/expanding_entry_card_item_alternate_icon_start_margin" />
+ <TextView
+ android:id="@+id/third_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ android:layout_marginStart="@dimen/expanding_entry_card_item_alternate_icon_start_margin"
+ style="@android:style/TextAppearance.Material.Body2"
+ android:visibility="gone" />
</view>
diff --git a/res/layout/people_activity.xml b/res/layout/people_activity.xml
index ce995cb3d..6b571dfa5 100644
--- a/res/layout/people_activity.xml
+++ b/res/layout/people_activity.xml
@@ -37,17 +37,5 @@
android:layout_below="@id/toolbar_parent"
/>
- <FrameLayout
- android:id="@+id/contacts_unavailable_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_below="@id/toolbar_parent"
- android:visibility="gone">
- <FrameLayout
- android:id="@+id/contacts_unavailable_container"
- android:layout_height="match_parent"
- android:layout_width="match_parent" />
- </FrameLayout>
-
<include layout="@layout/floating_action_button" />
</RelativeLayout>
diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml
index 21ecba589..dc99dfa4d 100644
--- a/res/values/cm_strings.xml
+++ b/res/values/cm_strings.xml
@@ -73,4 +73,9 @@
<string name="powered_by_provider">Powered by <xliff:g id="provider">%s</xliff:g></string>
+ <!-- InCall plugin directory search & invite -->
+ <string name="incall_plugin_directory_search">Search %1$s Directory</string>
+ <string name="incall_plugin_invite">INVITE</string>
+ <string name="incall_plugin_account_subheader">%1$s name</string>
+ <string name="incall_plugin_call_error"><xliff:g id="name">%s</xliff:g> cannot make this call</string>
</resources>
diff --git a/src/com/android/contacts/ContactsApplication.java b/src/com/android/contacts/ContactsApplication.java
index 798614c74..74fb9e5be 100644
--- a/src/com/android/contacts/ContactsApplication.java
+++ b/src/com/android/contacts/ContactsApplication.java
@@ -24,6 +24,7 @@ import android.content.ContentUris;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
+import android.os.Bundle;
import android.os.StrictMode;
import android.preference.PreferenceManager;
import android.provider.ContactsContract.Contacts;
@@ -35,6 +36,8 @@ import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.testing.InjectedServices;
import com.android.contacts.common.util.Constants;
import com.android.contacts.commonbind.analytics.AnalyticsUtil;
+import com.android.contacts.incall.InCallPluginHelper;
+import com.android.phone.common.incall.CallMethodHelper;
import com.google.common.annotations.VisibleForTesting;
@@ -123,6 +126,7 @@ public final class ContactsApplication extends Application {
}
AnalyticsUtil.initialize(this);
+ InCallPluginHelper.init(this);
}
private class DelayedInitializer extends AsyncTask<Void, Void, Void> {
diff --git a/src/com/android/contacts/StartCallResultReceiver.java b/src/com/android/contacts/StartCallResultReceiver.java
new file mode 100644
index 000000000..f1bf664b5
--- /dev/null
+++ b/src/com/android/contacts/StartCallResultReceiver.java
@@ -0,0 +1,42 @@
+package com.android.contacts.incall;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+
+public class StartCallResultReceiver extends ResultReceiver {
+ private static final String TAG = StartCallResultReceiver.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private WeakReference<Receiver> mReceiver;
+
+ public StartCallResultReceiver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (DEBUG) {
+ Log.d(TAG, "Result received resultCode: " + resultCode + " resultData: " + resultData);
+ }
+
+ if (mReceiver != null) {
+ Receiver receiver = mReceiver.get();
+ if (receiver != null) {
+ receiver.onReceiveResult(resultCode, resultData);
+ }
+ }
+ }
+
+ public interface Receiver {
+ public void onReceiveResult(int resultCode, Bundle resultData);
+ }
+
+ public void setReceiver(Receiver receiver) {
+ mReceiver = new WeakReference<Receiver>(receiver);
+ }
+
+} \ No newline at end of file
diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java
index 6a81d066b..97e3f1777 100644
--- a/src/com/android/contacts/activities/ActionBarAdapter.java
+++ b/src/com/android/contacts/activities/ActionBarAdapter.java
@@ -73,8 +73,10 @@ public class ActionBarAdapter implements OnCloseListener {
private static final String EXTRA_KEY_QUERY = "navBar.query";
private static final String EXTRA_KEY_SELECTED_TAB = "navBar.selectedTab";
private static final String EXTRA_KEY_SELECTED_MODE = "navBar.selectionMode";
+ private static final String EXTRA_KEY_TAB_COUNT = "navBar.tabCount";
private static final String PERSISTENT_LAST_TAB = "actionBarAdapter.lastTab";
+ private static final String PERSISTENT_LAST_TAB_COUNT = "actionBarAdapter.lastTabCount";
private boolean mSelectionMode;
private boolean mSearchMode;
@@ -117,6 +119,7 @@ public class ActionBarAdapter implements OnCloseListener {
}
private int mCurrentTab = TabState.DEFAULT;
+ private int mTabCount = TabState.COUNT;
public ActionBarAdapter(Activity activity, Listener listener, ActionBar actionBar,
View portraitTabs, View landscapeTabs, Toolbar toolbar) {
@@ -193,12 +196,13 @@ public class ActionBarAdapter implements OnCloseListener {
});
}
- public void initialize(Bundle savedState, ContactsRequest request) {
+
+ public void initialize(Bundle savedState, ContactsRequest request, int newTabCount) {
if (savedState == null) {
mSearchMode = request.isSearchMode();
mQueryString = request.getQueryString();
mCurrentTab = loadLastTabPreference();
- mSelectionMode = false;
+ mTabCount = loadLastTabCountPreference();
} else {
mSearchMode = savedState.getBoolean(EXTRA_KEY_SEARCH_MODE);
mSelectionMode = savedState.getBoolean(EXTRA_KEY_SELECTED_MODE);
@@ -206,8 +210,14 @@ public class ActionBarAdapter implements OnCloseListener {
// Just set to the field here. The listener will be notified by update().
mCurrentTab = savedState.getInt(EXTRA_KEY_SELECTED_TAB);
+ mTabCount = savedState.getInt(EXTRA_KEY_TAB_COUNT);
+ }
+ if (mTabCount != newTabCount) {
+ mTabCount = newTabCount;
+ saveLastTabCountPreference(mTabCount);
+ mCurrentTab = TabState.DEFAULT;
}
- if (mCurrentTab >= TabState.COUNT || mCurrentTab < 0) {
+ if (mCurrentTab >= mTabCount || mCurrentTab < 0) {
// Invalid tab index was saved (b/12938207). Restore the default.
mCurrentTab = TabState.DEFAULT;
}
@@ -254,21 +264,23 @@ public class ActionBarAdapter implements OnCloseListener {
/**
* Save the current tab selection, and notify the listener.
*/
- public void setCurrentTab(int tab) {
- setCurrentTab(tab, true);
+ public void setCurrentTab(int tab, int tabCount) {
+ setCurrentTab(tab, tabCount, true);
}
/**
* Save the current tab selection.
*/
- public void setCurrentTab(int tab, boolean notifyListener) {
- if (tab == mCurrentTab) {
+ public void setCurrentTab(int tab, int tabCount, boolean notifyListener) {
+ if (tab == mCurrentTab && tabCount == mTabCount) {
return;
}
mCurrentTab = tab;
+ mTabCount = tabCount;
if (notifyListener && mListener != null) mListener.onSelectedTabChanged();
saveLastTabPreference(mCurrentTab);
+ saveLastTabCountPreference(mTabCount);
}
public int getCurrentTab() {
@@ -550,6 +562,7 @@ public class ActionBarAdapter implements OnCloseListener {
outState.putBoolean(EXTRA_KEY_SELECTED_MODE, mSelectionMode);
outState.putString(EXTRA_KEY_QUERY, mQueryString);
outState.putInt(EXTRA_KEY_SELECTED_TAB, mCurrentTab);
+ outState.putInt(EXTRA_KEY_TAB_COUNT, mTabCount);
}
public void setFocusOnSearchView() {
@@ -578,6 +591,18 @@ public class ActionBarAdapter implements OnCloseListener {
}
}
+ private void saveLastTabCountPreference(int tabCount) {
+ mPrefs.edit().putInt(PERSISTENT_LAST_TAB_COUNT, tabCount).apply();
+ }
+
+ private int loadLastTabCountPreference() {
+ try {
+ return mPrefs.getInt(PERSISTENT_LAST_TAB_COUNT, TabState.COUNT);
+ } catch (ClassCastException e) {
+ return TabState.COUNT;
+ }
+ }
+
private void animateTabHeightChange(int start, int end) {
if (mPortraitTabs == null) {
return;
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index 36e805ccb..a58545c5b 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -499,6 +499,11 @@ public class ContactSelectionActivity extends ContactsActivity
}
@Override
+ public void onCallNumberDirectly(String phoneNumber, boolean isVideoCall, String mimeType) {
+ Log.w(TAG, "Unsupported call.");
+ }
+
+ @Override
public void onShortcutIntentCreated(Intent intent) {
returnPickerResult(intent);
}
diff --git a/src/com/android/contacts/activities/MultiPickContactActivity.java b/src/com/android/contacts/activities/MultiPickContactActivity.java
index ee7614b88..ac22f68b7 100644
--- a/src/com/android/contacts/activities/MultiPickContactActivity.java
+++ b/src/com/android/contacts/activities/MultiPickContactActivity.java
@@ -104,6 +104,7 @@ import com.android.contacts.common.list.ContactsSectionIndexer;
import com.android.contacts.common.list.DefaultContactListAdapter;
import com.android.contacts.common.MoreContactUtils;
import com.android.contacts.common.model.account.SimAccountType;
+import com.cyanogen.ambient.incall.CallLogConstants;
import java.util.ArrayList;
import java.util.Iterator;
@@ -928,7 +929,7 @@ public class MultiPickContactActivity extends ListActivity implements
break;
case MODE_DEFAULT_CALL:
case MODE_SEARCH_CALL:
- uri = Calls.CONTENT_URI_WITH_VOICEMAIL;
+ uri = CallLogConstants.CONTENT_ALL_URI_WITH_VOICEMAIL;
break;
case MODE_DEFAULT_SIM:
case MODE_SEARCH_SIM: {
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index bad3f6996..f548976eb 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -20,12 +20,15 @@ import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
+import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
import android.content.ContentUris;
import android.content.Context;
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
@@ -55,6 +58,8 @@ import android.widget.Toast;
import android.widget.Toolbar;
import com.android.contacts.ContactsActivity;
+import com.android.contacts.incall.InCallPluginHelper;
+import com.android.contacts.incall.InCallPluginInfo;
import com.android.contacts.R;
import com.android.contacts.activities.ActionBarAdapter.TabState;
import com.android.contacts.common.ContactsUtils;
@@ -92,7 +97,7 @@ import com.android.contacts.list.OnContactsUnavailableActionListener;
import com.android.contacts.list.ProviderStatusWatcher;
import com.android.contacts.list.ProviderStatusWatcher.ProviderStatusListener;
import com.android.contacts.common.list.ViewPagerTabs;
-import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.list.PluginContactBrowseListFragment;
import com.android.contacts.preference.ContactsPreferenceActivity;
import com.android.contacts.common.SimContactsConstants;
import com.android.contacts.common.util.AccountFilterUtil;
@@ -104,11 +109,16 @@ import com.android.contacts.common.vcard.ExportVCardActivity;
import com.android.contacts.common.vcard.VCardCommonArguments;
import com.android.contacts.util.DialogManager;
import com.android.contactsbind.HelpUtils;
+import com.android.phone.common.incall.CallMethodHelper;
+import com.android.phone.common.incall.CallMethodInfo;
+import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.Locale;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@@ -126,9 +136,11 @@ public class PeopleActivity extends ContactsActivity implements
JoinContactsListener {
private static final String TAG = "PeopleActivity";
+ private static final boolean DEBUG = false;
public static String EDITABLE_KEY = "search_contacts";
private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!";
+ private static final int INCALL_PLUGIN_LOADER_ID = 0;
// These values needs to start at 2. See {@link ContactEntryListFragment}.
private static final int SUBACTIVITY_ACCOUNT_FILTER = 2;
@@ -144,7 +156,6 @@ public class PeopleActivity extends ContactsActivity implements
private GroupDetailFragment mGroupDetailFragment;
private final GroupDetailFragmentListener mGroupDetailFragmentListener =
new GroupDetailFragmentListener();
- private View mFloatingActionButtonContainer;
private boolean wasLastFabAnimationScaleIn = false;
private ContactTileListFragment.Listener mFavoritesFragmentListener =
@@ -152,7 +163,7 @@ public class PeopleActivity extends ContactsActivity implements
private ContactListFilterController mContactListFilterController;
- private ContactsUnavailableFragment mContactsUnavailableFragment;
+ private boolean mAccountUnavailable;
private ProviderStatusWatcher mProviderStatusWatcher;
private Integer mProviderStatus;
@@ -164,16 +175,27 @@ public class PeopleActivity extends ContactsActivity implements
private MultiSelectContactsListFragment mAllFragment;
private ContactTileListFragment mFavoritesFragment;
private GroupBrowseListFragment mGroupsFragment;
+ private ContactsUnavailableFragment mAllUnavailableFragment;
+ private ContactsUnavailableFragment mFavoritesUnavailableFragment;
+ private ContactsUnavailableFragment mGroupsUnavailableFragment;
+ private List<InCallPluginInfo> mPluginTabInfo = new ArrayList<InCallPluginInfo>();
+ private int mPluginLength;
+ private int mTabStateGroup = TabState.GROUPS;
/** ViewPager for swipe */
private ViewPager mTabPager;
private ViewPagerTabs mViewPagerTabs;
private TabPagerAdapter mTabPagerAdapter;
- private String[] mTabTitles;
+ private List<TabEntry> mTabTitles;
+ private static final String CALL_METHOD_HELPER_SUBSCRIBER_ID = "PeopleActivity";
private final TabPagerListener mTabPagerListener = new TabPagerListener();
-
+ private int mPageStateCount; // total number of pages
private boolean mEnableDebugMenuOptions;
+ /* Floating action button */
+ private View mFloatingActionButtonContainer;
+ private ImageButton mFloatingActionButton;
+
private ExportToSimThread mExportThread = null;
/**
* True if this activity instance is a re-created one. i.e. set true after orientation change.
@@ -202,6 +224,17 @@ public class PeopleActivity extends ContactsActivity implements
private ArrayList<String[]> mContactList;
private BroadcastReceiver mExportToSimCompleteListener = null;
+ private BroadcastReceiver mListenForPluginUpdates = null;
+ private BroadcastReceiver mAuthUpdateListener = null;
+
+ final String FAVORITE_TAG = "tab-pager-favorite";
+ final String ALL_TAG = "tab-pager-all";
+ final String GROUPS_TAG = "tab-pager-groups";
+ final String FAVORITE_UNAVAILABLE_TAG = "tab-pager-favorite-unav";
+ final String ALL_UNAVAILABLE_TAG = "tab-pager-all-unav";
+ final String GROUPS_UNAVAILABLE_TAG = "tab-pager-groups-unav";
+ private static final String KEY_PLUGIN_INFO_LIST = "pluginInfoList";
+ SharedPreferences mPrefs;
public PeopleActivity() {
mInstanceId = sNextInstanceId.getAndIncrement();
@@ -228,33 +261,13 @@ public class PeopleActivity extends ContactsActivity implements
return ContactsUtils.areGroupWritableAccountsAvailable(this);
}
- /**
- * Initialize fragments that are (or may not be) in the layout.
- *
- * For the fragments that are in the layout, we initialize them in
- * {@link #createViewsAndFragments(Bundle)} after inflating the layout.
- *
- * However, the {@link ContactsUnavailableFragment} is a special fragment which may not
- * be in the layout, so we have to do the initialization here.
- *
- * The ContactsUnavailableFragment is always created at runtime.
- */
- @Override
- public void onAttachFragment(Fragment fragment) {
- if (fragment instanceof ContactsUnavailableFragment) {
- mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment;
- mContactsUnavailableFragment.setOnContactsUnavailableActionListener(
- new ContactsUnavailableFragmentListener());
- }
- }
-
@Override
protected void onCreate(Bundle savedState) {
if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate start");
}
super.onCreate(savedState);
-
+ mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
if (RequestPermissionsActivity.startPermissionActivity(this)) {
return;
}
@@ -276,7 +289,7 @@ public class PeopleActivity extends ContactsActivity implements
Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate finish");
}
getWindow().setBackgroundDrawable(null);
- registerReceiver();
+ registerReceivers();
}
@Override
@@ -286,7 +299,7 @@ public class PeopleActivity extends ContactsActivity implements
finish();
return;
}
- mActionBarAdapter.initialize(null, mRequest);
+ mActionBarAdapter.initialize(null, mRequest, mPageStateCount);
mContactListFilterController.checkFilterValidity(false);
@@ -296,7 +309,7 @@ public class PeopleActivity extends ContactsActivity implements
invalidateOptionsMenuIfNeeded();
}
- private void registerReceiver() {
+ private void registerReceivers() {
mExportToSimCompleteListener = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
@@ -306,6 +319,7 @@ public class PeopleActivity extends ContactsActivity implements
}
}
};
+
IntentFilter exportCompleteFilter = new IntentFilter(
SimContactsConstants.INTENT_EXPORT_COMPLETE);
registerReceiver(mExportToSimCompleteListener, exportCompleteFilter);
@@ -344,19 +358,58 @@ public class PeopleActivity extends ContactsActivity implements
private void createViewsAndFragments(Bundle savedState) {
// Disable the ActionBar so that we can use a Toolbar. This needs to be called before
// setContentView().
+
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.people_activity);
+ // Configure action button, need to initialize early before ViewPager sets the
+ // visibility depending on the fragment type
+ mFloatingActionButtonContainer = findViewById(R.id.floating_action_button_container);
+ mFloatingActionButton = (ImageButton) findViewById(R.id.floating_action_button);
+ mFloatingActionButton.setOnClickListener(this);
+ mFloatingActionButtonController = new FloatingActionButtonController(this,
+ mFloatingActionButtonContainer, mFloatingActionButton);
+
final FragmentManager fragmentManager = getFragmentManager();
// Hide all tabs (the current tab will later be reshown once a tab is selected)
final FragmentTransaction transaction = fragmentManager.beginTransaction();
- mTabTitles = new String[TabState.COUNT];
- mTabTitles[TabState.FAVORITES] = getString(R.string.favorites_tab_label);
- mTabTitles[TabState.ALL] = getString(R.string.all_contacts_tab_label);
- mTabTitles[TabState.GROUPS] = getString(R.string.contacts_groups_label);
+ mTabTitles = new LinkedList<TabEntry>();
+ mTabTitles.add(TabState.FAVORITES, new TabEntry(FAVORITE_TAG, getString(R.string
+ .favorites_tab_label)));
+ mTabTitles.add(TabState.ALL, new TabEntry(ALL_TAG, getString(R.string
+ .all_contacts_tab_label)));
+ mTabTitles.add(TabState.GROUPS,new TabEntry(GROUPS_TAG, getString(R.string
+ .contacts_groups_label)));
+
+ if (savedState != null) {
+ // Reconstruct the plugin info list
+ List<InCallPluginInfo> restoreList = savedState.getParcelableArrayList
+ (KEY_PLUGIN_INFO_LIST);
+ if (restoreList != null) {
+ mPluginTabInfo = restoreList;
+ }
+ mPluginLength = mPluginTabInfo.size();
+ for (int i = 0; i < mPluginLength; i++) {
+ InCallPluginInfo pluginInfo = mPluginTabInfo.get(i);
+ mTabTitles.add(TabState.GROUPS + i, new TabEntry(pluginInfo.mTabTag,
+ pluginInfo.mCallMethodInfo.mName));
+ pluginInfo.mFragment = (PluginContactBrowseListFragment) fragmentManager
+ .findFragmentByTag(pluginInfo.mTabTag);
+ pluginInfo.mFragment.setOnContactListActionListener
+ (new PluginContactBrowserActionListener());
+ pluginInfo.mFragment.updateInCallPluginInfo(pluginInfo);
+ }
+ } else {
+ // Init plugin info list
+ mPluginTabInfo.clear();
+ mPluginLength = 0;
+ }
+ mPageStateCount = TabState.COUNT + mPluginLength;
+ mTabStateGroup = TabState.GROUPS + mPluginLength;
+
mTabPager = getView(R.id.tab_pager);
mTabPagerAdapter = new TabPagerAdapter();
mTabPager.setAdapter(mTabPagerAdapter);
@@ -393,15 +446,36 @@ public class PeopleActivity extends ContactsActivity implements
fragmentManager.findFragmentByTag(ALL_TAG);
mGroupsFragment = (GroupBrowseListFragment)
fragmentManager.findFragmentByTag(GROUPS_TAG);
+ mFavoritesUnavailableFragment = (ContactsUnavailableFragment) fragmentManager
+ .findFragmentByTag(FAVORITE_UNAVAILABLE_TAG);
+ mAllUnavailableFragment = (ContactsUnavailableFragment) fragmentManager
+ .findFragmentByTag(ALL_UNAVAILABLE_TAG);
+ mGroupsUnavailableFragment = (ContactsUnavailableFragment) fragmentManager
+ .findFragmentByTag(GROUPS_UNAVAILABLE_TAG);
+
if (mFavoritesFragment == null) {
mFavoritesFragment = new ContactTileListFragment();
mAllFragment = new MultiSelectContactsListFragment();
mGroupsFragment = new GroupBrowseListFragment();
+ mFavoritesUnavailableFragment = new ContactsUnavailableFragment();
+ mAllUnavailableFragment = new ContactsUnavailableFragment();
+ mGroupsUnavailableFragment = new ContactsUnavailableFragment();
+ mFavoritesUnavailableFragment.setOnContactsUnavailableActionListener(
+ new ContactsUnavailableFragmentListener());
+ mAllUnavailableFragment.setOnContactsUnavailableActionListener(
+ new ContactsUnavailableFragmentListener());
+ mGroupsUnavailableFragment.setOnContactsUnavailableActionListener(
+ new ContactsUnavailableFragmentListener());
transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG);
transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG);
transaction.add(R.id.tab_pager, mGroupsFragment, GROUPS_TAG);
+ transaction.add(R.id.tab_pager, mFavoritesUnavailableFragment,
+ FAVORITE_UNAVAILABLE_TAG);
+ transaction.add(R.id.tab_pager, mAllUnavailableFragment, ALL_UNAVAILABLE_TAG);
+ transaction.add(R.id.tab_pager, mGroupsUnavailableFragment, GROUPS_UNAVAILABLE_TAG);
+
}
mFavoritesFragment.setListener(mFavoritesFragmentListener);
@@ -416,29 +490,28 @@ public class PeopleActivity extends ContactsActivity implements
transaction.hide(mFavoritesFragment);
transaction.hide(mAllFragment);
transaction.hide(mGroupsFragment);
+ transaction.hide(mFavoritesUnavailableFragment);
+ transaction.hide(mAllUnavailableFragment);
+ transaction.hide(mGroupsUnavailableFragment);
transaction.commitAllowingStateLoss();
fragmentManager.executePendingTransactions();
+ mFavoritesUnavailableFragment.setMessageText(R.string.listTotalAllContactsZeroStarred, -1);
+ mGroupsUnavailableFragment.setMessageText(R.string.noGroups,
+ areGroupWritableAccountsAvailable() ? -1 : R.string.noAccounts);
+ mAllUnavailableFragment.setMessageText(R.string.noContacts, -1);
+
// Setting Properties after fragment is created
mFavoritesFragment.setDisplayType(DisplayType.STREQUENT);
mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar(),
portraitViewPagerTabs, landscapeViewPagerTabs, toolbar);
- mActionBarAdapter.initialize(savedState, mRequest);
-
+ mActionBarAdapter.initialize(savedState, mRequest, mPageStateCount);
+ initializeFabVisibility();
// Add shadow under toolbar
ViewUtil.addRectangularOutlineProvider(findViewById(R.id.toolbar_parent), getResources());
- // Configure floating action button
- mFloatingActionButtonContainer = findViewById(R.id.floating_action_button_container);
- final ImageButton floatingActionButton
- = (ImageButton) findViewById(R.id.floating_action_button);
- floatingActionButton.setOnClickListener(this);
- mFloatingActionButtonController = new FloatingActionButtonController(this,
- mFloatingActionButtonContainer, floatingActionButton);
- initializeFabVisibility();
-
invalidateOptionsMenuIfNeeded();
}
@@ -468,6 +541,7 @@ public class PeopleActivity extends ContactsActivity implements
@Override
protected void onPause() {
+ InCallPluginHelper.unsubscribe(CALL_METHOD_HELPER_SUBSCRIBER_ID);
mOptionsMenuContactsAvailable = false;
mProviderStatusWatcher.stop();
super.onPause();
@@ -479,6 +553,14 @@ public class PeopleActivity extends ContactsActivity implements
protected void onResume() {
super.onResume();
+ onResumeInit();
+ if (InCallPluginHelper.subscribe(CALL_METHOD_HELPER_SUBSCRIBER_ID,
+ pluginsUpdatedReceiver)) {
+ InCallPluginHelper.refreshDynamicItems();
+ }
+ }
+
+ private synchronized void onResumeInit() {
mProviderStatusWatcher.start();
updateViewConfiguration(true);
@@ -510,6 +592,7 @@ public class PeopleActivity extends ContactsActivity implements
if (mExportToSimCompleteListener != null) {
unregisterReceiver(mExportToSimCompleteListener);
}
+
super.onDestroy();
}
@@ -550,14 +633,14 @@ public class PeopleActivity extends ContactsActivity implements
tabToOpen = TabState.ALL;
break;
case ContactsRequest.ACTION_GROUP:
- tabToOpen = TabState.GROUPS;
+ tabToOpen = mTabStateGroup;
break;
default:
tabToOpen = -1;
break;
}
if (tabToOpen != -1) {
- mActionBarAdapter.setCurrentTab(tabToOpen);
+ mActionBarAdapter.setCurrentTab(tabToOpen, mPageStateCount);
}
if (filter != null) {
@@ -675,7 +758,7 @@ public class PeopleActivity extends ContactsActivity implements
* Updates the fragment/view visibility according to the current mode, such as
* {@link ActionBarAdapter#isSearchMode()} and {@link ActionBarAdapter#getCurrentTab()}.
*/
- private void updateFragmentsVisibility() {
+ private synchronized void updateFragmentsVisibility() {
int tab = mActionBarAdapter.getCurrentTab();
if (mActionBarAdapter.isSearchMode() || mActionBarAdapter.isSelectionMode()) {
@@ -693,34 +776,11 @@ public class PeopleActivity extends ContactsActivity implements
mAllFragment.displayCheckBoxes(false);
}
invalidateOptionsMenu();
- showEmptyStateForTab(tab);
- if (tab == TabState.GROUPS) {
+ if (tab == mTabStateGroup) {
mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
}
}
- private void showEmptyStateForTab(int tab) {
- if (mContactsUnavailableFragment != null) {
- switch (getTabPositionForTextDirection(tab)) {
- case TabState.FAVORITES:
- mContactsUnavailableFragment.setMessageText(
- R.string.listTotalAllContactsZeroStarred, -1);
- break;
- case TabState.GROUPS:
- mContactsUnavailableFragment.setMessageText(R.string.noGroups,
- areGroupWritableAccountsAvailable() ? -1 : R.string.noAccounts);
- break;
- case TabState.ALL:
- mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1);
- break;
- }
- // When using the mContactsUnavailableFragment the ViewPager doesn't contain two views.
- // Therefore, we have to trick the ViewPagerTabs into thinking we have changed tabs
- // when the mContactsUnavailableFragment changes. Otherwise the tab strip won't move.
- mViewPagerTabs.onPageScrolled(tab, 0, 0);
- }
- }
-
private class TabPagerListener implements ViewPager.OnPageChangeListener {
// This package-protected constructor is here because of a possible compiler bug.
@@ -755,10 +815,9 @@ public class PeopleActivity extends ContactsActivity implements
public void onPageSelected(int position) {
// Make sure not in the search mode, in which case position != TabState.ordinal().
if (!mTabPagerAdapter.areTabsHidden()) {
- mActionBarAdapter.setCurrentTab(position, false);
+ mActionBarAdapter.setCurrentTab(position, mPageStateCount, false);
mViewPagerTabs.onPageSelected(position);
- showEmptyStateForTab(position);
- if (position == TabState.GROUPS) {
+ if (position == mTabStateGroup) {
mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
}
invalidateOptionsMenu();
@@ -802,7 +861,7 @@ public class PeopleActivity extends ContactsActivity implements
@Override
public int getCount() {
- return mAreTabsHiddenInTabPager ? 1 : TabState.COUNT;
+ return mAreTabsHiddenInTabPager ? 1 : mPageStateCount;
}
/** Gets called when the number of items changes. */
@@ -813,14 +872,22 @@ public class PeopleActivity extends ContactsActivity implements
return 0; // Only 1 page in search mode
}
} else {
- if (object == mFavoritesFragment) {
+ if ((!mAccountUnavailable && object == mFavoritesFragment) ||
+ (mAccountUnavailable && object == mFavoritesUnavailableFragment)) {
return getTabPositionForTextDirection(TabState.FAVORITES);
}
- if (object == mAllFragment) {
+ if ((!mAccountUnavailable && object == mAllFragment) || (mAccountUnavailable &&
+ object == mAllUnavailableFragment)) {
return getTabPositionForTextDirection(TabState.ALL);
}
- if (object == mGroupsFragment) {
- return TabState.GROUPS;
+ for (int i = 0; i < mPluginLength; i++) {
+ if (object == mPluginTabInfo.get(i).mFragment) {
+ return getTabPositionForTextDirection(TabState.GROUPS + i);
+ }
+ }
+ if ((!mAccountUnavailable && object == mGroupsFragment) || (mAccountUnavailable &&
+ object == mGroupsUnavailableFragment)) {
+ return mTabStateGroup;
}
}
return POSITION_NONE;
@@ -842,11 +909,16 @@ public class PeopleActivity extends ContactsActivity implements
return mAllFragment;
} else {
if (position == TabState.FAVORITES) {
- return mFavoritesFragment;
+ return mAccountUnavailable ? mFavoritesUnavailableFragment : mFavoritesFragment;
} else if (position == TabState.ALL) {
- return mAllFragment;
- } else if (position == TabState.GROUPS) {
- return mGroupsFragment;
+ return mAccountUnavailable ? mAllUnavailableFragment : mAllFragment;
+ } else if (position == (mPageStateCount - 1)) {
+ return mAccountUnavailable ? mGroupsUnavailableFragment : mGroupsFragment;
+ } else {
+ int pluginOffset = position - TabState.GROUPS;
+ if (pluginOffset >= 0 && pluginOffset < mPluginTabInfo.size()) {
+ return mPluginTabInfo.get(pluginOffset).mFragment;
+ }
}
}
throw new IllegalArgumentException("position: " + position);
@@ -912,7 +984,7 @@ public class PeopleActivity extends ContactsActivity implements
@Override
public CharSequence getPageTitle(int position) {
- return mTabTitles[position];
+ return mTabTitles.get(position).mTitle;
}
}
@@ -970,18 +1042,13 @@ public class PeopleActivity extends ContactsActivity implements
&& (mProviderStatus.equals(providerStatus))) return;
mProviderStatus = providerStatus;
- View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view);
-
if (mProviderStatus.equals(ProviderStatus.STATUS_NORMAL)) {
// Ensure that the mTabPager is visible; we may have made it invisible below.
- contactsUnavailableView.setVisibility(View.GONE);
- if (mTabPager != null) {
- mTabPager.setVisibility(View.VISIBLE);
- }
-
+ mAccountUnavailable = false;
if (mAllFragment != null) {
mAllFragment.setEnabled(true);
}
+ mTabPagerAdapter.notifyDataSetChanged();
} else {
// If there are no accounts on the device and we should show the "no account" prompt
// (based on {@link SharedPreferences}), then launch the account setup activity so the
@@ -998,33 +1065,46 @@ public class PeopleActivity extends ContactsActivity implements
AccountPromptUtils.launchAccountPrompt(this);
return;
}
+ mAccountUnavailable = true;
// Otherwise, continue setting up the page so that the user can still use the app
// without an account.
if (mAllFragment != null) {
mAllFragment.setEnabled(false);
}
- if (mContactsUnavailableFragment == null) {
- mContactsUnavailableFragment = new ContactsUnavailableFragment();
- mContactsUnavailableFragment.setOnContactsUnavailableActionListener(
- new ContactsUnavailableFragmentListener());
- getFragmentManager().beginTransaction()
- .replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment)
- .commitAllowingStateLoss();
- }
- mContactsUnavailableFragment.updateStatus(mProviderStatus);
+ mAllUnavailableFragment.updateStatus(mProviderStatus);
+ mFavoritesUnavailableFragment.updateStatus(mProviderStatus);
+ mGroupsUnavailableFragment.updateStatus(mProviderStatus);
+ mTabPagerAdapter.notifyDataSetChanged();
+ }
- // Show the contactsUnavailableView, and hide the mTabPager so that we don't
- // see it sliding in underneath the contactsUnavailableView at the edges.
- contactsUnavailableView.setVisibility(View.VISIBLE);
- if (mTabPager != null) {
- mTabPager.setVisibility(View.GONE);
- }
+ invalidateOptionsMenuIfNeeded();
+ }
+ private final class PluginContactBrowserActionListener implements
+ OnContactBrowserActionListener {
+ PluginContactBrowserActionListener() {}
+ @Override
+ public void onSelectionChange() {}
- showEmptyStateForTab(mActionBarAdapter.getCurrentTab());
+ @Override
+ public void onViewContactAction(Uri contactLookupUri) {
+ final Intent intent = ImplicitIntentsUtil.composeQuickContactIntent(contactLookupUri,
+ QuickContactActivity.MODE_FULLY_EXPANDED);
+ ImplicitIntentsUtil.startActivityInApp(PeopleActivity.this, intent);
}
- invalidateOptionsMenuIfNeeded();
+ @Override
+ public void onDeleteContactAction(Uri contactUri) {
+ ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false);
+ }
+
+ @Override
+ public void onFinishAction() {
+ onBackPressed();
+ }
+
+ @Override
+ public void onInvalidSelection() {}
}
private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
@@ -1245,6 +1325,7 @@ public class PeopleActivity extends ContactsActivity implements
final boolean isSearchOrSelectionMode = mActionBarAdapter.isSearchMode()
|| mActionBarAdapter.isSelectionMode();
+ boolean isPlugin = false;
if (isSearchOrSelectionMode) {
addGroupMenu.setVisible(false);
contactsFilterMenu.setVisible(false);
@@ -1252,18 +1333,16 @@ public class PeopleActivity extends ContactsActivity implements
helpMenu.setVisible(false);
makeMenuItemVisible(menu, R.id.menu_delete, false);
} else {
- switch (getTabPositionForTextDirection(mActionBarAdapter.getCurrentTab())) {
- case TabState.FAVORITES:
- addGroupMenu.setVisible(false);
- contactsFilterMenu.setVisible(false);
- clearFrequentsMenu.setVisible(hasFrequents());
- break;
- case TabState.ALL:
- addGroupMenu.setVisible(false);
- contactsFilterMenu.setVisible(true);
- clearFrequentsMenu.setVisible(false);
- break;
- case TabState.GROUPS:
+ int tabPosition = getTabPositionForTextDirection(mActionBarAdapter.getCurrentTab());
+ if (tabPosition == TabState.FAVORITES) {
+ addGroupMenu.setVisible(false);
+ contactsFilterMenu.setVisible(false);
+ clearFrequentsMenu.setVisible(hasFrequents());
+ } else if (tabPosition == TabState.ALL) {
+ addGroupMenu.setVisible(false);
+ contactsFilterMenu.setVisible(true);
+ clearFrequentsMenu.setVisible(false);
+ } else if (tabPosition == mTabStateGroup) {
// Do not display the "new group" button if no accounts are available
if (areGroupWritableAccountsAvailable()) {
addGroupMenu.setVisible(true);
@@ -1272,10 +1351,38 @@ public class PeopleActivity extends ContactsActivity implements
}
contactsFilterMenu.setVisible(false);
clearFrequentsMenu.setVisible(false);
+ } else if (mPluginLength > 0 && tabPosition >= TabState.GROUPS){
+ // plugin tab
+ int pluginIndex = tabPosition - TabState.GROUPS;
+ InCallPluginInfo pluginInfo = mPluginTabInfo.get(pluginIndex);
+ // floating button state
+ if (pluginInfo.mCallMethodInfo.mIsAuthenticated) {
+ mFloatingActionButtonContainer.setVisibility(View.VISIBLE);
+ } else {
+ mFloatingActionButtonContainer.setVisibility(View.GONE);
+ }
+ // menu
+ addGroupMenu.setVisible(false);
+ contactsFilterMenu.setVisible(false);
+ clearFrequentsMenu.setVisible(false);
+ makeMenuItemVisible(menu, R.id.menu_delete, false);
+ isPlugin = true;
+ }
+ if (!isPlugin) {
+ mFloatingActionButtonContainer.setVisibility(View.VISIBLE);
}
+
helpMenu.setVisible(HelpUtils.isHelpAndFeedbackAvailable());
}
final boolean showMiscOptions = !isSearchOrSelectionMode;
+ if (!isPlugin) {
+ makeMenuItemVisible(menu, R.id.menu_search, showMiscOptions);
+ mFloatingActionButton.setImageDrawable(getResources().getDrawable(R.drawable
+ .ic_person_add_24dp));
+ } else {
+ mFloatingActionButton.setImageDrawable(getResources().getDrawable(R.drawable
+ .ic_add));
+ }
makeMenuItemVisible(menu, R.id.menu_search, showMiscOptions);
makeMenuItemVisible(menu, R.id.menu_import_export, showMiscOptions);
makeMenuItemVisible(menu, R.id.menu_accounts, showMiscOptions);
@@ -1642,6 +1749,10 @@ public class PeopleActivity extends ContactsActivity implements
if (mTabPager != null) {
mTabPager.setOnPageChangeListener(null);
}
+ if (mPluginLength > 0) {
+ outState.putParcelableArrayList(KEY_PLUGIN_INFO_LIST, (ArrayList<InCallPluginInfo>)
+ mPluginTabInfo);
+ }
}
@Override
@@ -1664,16 +1775,31 @@ public class PeopleActivity extends ContactsActivity implements
public void onClick(View view) {
switch (view.getId()) {
case R.id.floating_action_button:
- Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
- Bundle extras = getIntent().getExtras();
- if (extras != null) {
- intent.putExtras(extras);
- }
- try {
- ImplicitIntentsUtil.startActivityInApp(PeopleActivity.this, intent);
- } catch (ActivityNotFoundException ex) {
- Toast.makeText(PeopleActivity.this, R.string.missing_app,
- Toast.LENGTH_SHORT).show();
+ int tabPosition = getTabPositionForTextDirection(mActionBarAdapter.getCurrentTab());
+ if (mPluginLength > 0 && tabPosition >= TabState.GROUPS &&
+ tabPosition != mTabStateGroup) {
+ // plugin tab
+ int pluginIndex = tabPosition - TabState.GROUPS;
+ InCallPluginInfo pluginInfo = mPluginTabInfo.get(pluginIndex);
+ if (pluginInfo.mCallMethodInfo.mDefaultDirectorySearchIntent != null) {
+ try {
+ pluginInfo.mCallMethodInfo.mDefaultDirectorySearchIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.d(TAG, "directory search exception: ", e);
+ }
+ }
+ } else {
+ Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ try {
+ ImplicitIntentsUtil.startActivityInApp(PeopleActivity.this, intent);
+ } catch (ActivityNotFoundException ex) {
+ Toast.makeText(PeopleActivity.this, R.string.missing_app,
+ Toast.LENGTH_SHORT).show();
+ }
}
break;
default:
@@ -1686,8 +1812,161 @@ public class PeopleActivity extends ContactsActivity implements
*/
private int getTabPositionForTextDirection(int position) {
if (isRTL()) {
- return TabState.COUNT - 1 - position;
+ return mPageStateCount - 1 - position;
}
return position;
}
+
+ private class TabEntry {
+ public String mTag;
+ public String mTitle;
+
+ public TabEntry(String tag, String title) {
+ mTag = tag;
+ mTitle = title;
+ }
+ }
+
+ /*
+ * peforms a look up using the tab tag among the ViewPagerTabs
+ */
+ private int lookupTabTag(String tabTag) {
+ int size = mTabTitles.size();
+ for (int i = 0; i < size; i++) {
+ TabEntry tab = mTabTitles.get(i);
+ if (TextUtils.equals(tab.mTag, tabTag)) {
+ return i;
+ }
+ }
+ return TabState.ALL;
+ }
+
+ private CallMethodHelper.CallMethodReceiver pluginsUpdatedReceiver =
+ new CallMethodHelper.CallMethodReceiver() {
+ @Override
+ public void onChanged(HashMap<ComponentName, CallMethodInfo> callMethodInfos) {
+ updatePlugins(callMethodInfos);
+ }
+ };
+
+ // Global CallMethod map that keeps track of the currently displayed plugins
+ HashMap<ComponentName, CallMethodInfo> mCallMethodMap =
+ new HashMap<ComponentName, CallMethodInfo>();
+
+ private InCallPluginInfo getPluginInfo(ComponentName cm) {
+ for (int i = 0; i < mPluginTabInfo.size(); i++) {
+ if (mPluginTabInfo.get(i).mCallMethodInfo.mComponent.equals(cm)) {
+ return mPluginTabInfo.get(i);
+ }
+ }
+ return null;
+ }
+
+ private void removePluginInfo(ComponentName cn) {
+ for (int i = 0; i < mPluginTabInfo.size(); i++) {
+ if (mPluginTabInfo.get(i).mCallMethodInfo.mComponent.equals(cn)) {
+ mPluginTabInfo.remove(i);
+ break;
+ }
+ }
+ }
+
+ private void removeTabTitle(ComponentName cn) {
+ for (int i = 0; i < mTabTitles.size(); i++) {
+ if (mTabTitles.get(i).mTag.equals(cn.toShortString())) {
+ mTabTitles.remove(i);
+ return;
+ }
+ }
+ }
+
+ private synchronized void updatePlugins(HashMap<ComponentName, CallMethodInfo>
+ callMethodInfo) {
+ HashMap<ComponentName, CallMethodInfo> newCmMap = (HashMap<ComponentName,
+ CallMethodInfo>) CallMethodHelper.getAllEnabledCallMethods().clone();
+ if (DEBUG) Log.d(TAG, "updatePlugins newCmMap size:" + newCmMap.size());
+ boolean updateTabs = false;
+ String lastSelectedTabTag = mTabTitles.get(mActionBarAdapter.getCurrentTab()).mTag;
+ boolean executeFragTransact = false;
+ FragmentTransaction transaction;
+ FragmentManager fragmentManager = getFragmentManager();
+
+ for (ComponentName cn : mCallMethodMap.keySet()) {
+ CallMethodInfo cm = mCallMethodMap.get(cn);
+ if (newCmMap.containsKey(cn)) {
+ // Check if update needed
+ CallMethodInfo newCm = newCmMap.remove(cn);
+ if (!newCm.equals(cm) || newCm.mIsAuthenticated != cm.mIsAuthenticated) {
+ InCallPluginInfo pluginInfo = getPluginInfo(cn);
+ pluginInfo.mCallMethodInfo = newCm;
+ pluginInfo.mFragment.updateInCallPluginInfo(pluginInfo);
+ mCallMethodMap.put(cn, newCm);
+ }
+ } else {
+ // Remove the tab associated with a plugin that's no longer available
+ updateTabs = true;
+ removeTabTitle(cn);
+ removePluginInfo(cn);
+ InCallPluginInfo removePlugin = getPluginInfo(cn);
+ if (removePlugin != null) {
+ transaction = fragmentManager.beginTransaction();
+ transaction.remove(removePlugin.mFragment);
+ transaction.commitAllowingStateLoss();
+ executeFragTransact = true;
+ }
+ }
+ }
+ // add newly added tab from newCmMap (existing tab already removed in the logic above)
+ for (ComponentName cn : newCmMap.keySet()) {
+ InCallPluginInfo newInfo = new InCallPluginInfo();
+ newInfo.mTabTag = cn.toShortString();
+ PluginContactBrowseListFragment frag = (PluginContactBrowseListFragment)
+ getFragmentManager().findFragmentByTag(newInfo.mTabTag);
+ if (frag == null) {
+ newInfo.mCallMethodInfo = newCmMap.get(cn);
+ mCallMethodMap.put(cn, newInfo.mCallMethodInfo);
+ mPluginTabInfo.add(0, newInfo);
+ newInfo.mFragment = new PluginContactBrowseListFragment();
+ transaction = fragmentManager.beginTransaction();
+ transaction.add(R.id.tab_pager, newInfo.mFragment, newInfo.mTabTag);
+ transaction.hide(newInfo.mFragment);
+ transaction.commitAllowingStateLoss();
+ newInfo.mFragment.updateInCallPluginInfo(newInfo);
+ newInfo.mFragment.setOnContactListActionListener
+ (new PluginContactBrowserActionListener());
+ mTabTitles.add(TabState.GROUPS,
+ new TabEntry(newInfo.mTabTag, newInfo.mCallMethodInfo.mName));
+ updateTabs = true;
+ executeFragTransact = true;
+ }
+ }
+
+ if (executeFragTransact) {
+ fragmentManager.executePendingTransactions();
+ }
+ // update holders
+ if (updateTabs) {
+ mPluginLength = mPluginTabInfo.size();
+ mPageStateCount = TabState.COUNT + mPluginLength;
+ mTabStateGroup = TabState.GROUPS + mPluginLength;
+ // update ViewPager
+ mActionBarAdapter.setListener(null);
+ if (mTabPager != null) {
+ mTabPager.setOnPageChangeListener(null);
+ }
+
+ mTabPagerAdapter.notifyDataSetChanged();
+ // re-add all the pages to ViewPagerTabs, including the new plugin tabs
+ mViewPagerTabs.setViewPager(mTabPager);
+ // restore last tab by unique tab tag, if it exsits in the tabs
+ mActionBarAdapter.setCurrentTab(lookupTabTag(lastSelectedTabTag), mPageStateCount,
+ false);
+ // need to force the ViewPagerTabs to refocus on the right tab, since setViewPager
+ // above causes a state loss
+ mViewPagerTabs.onPageSelected(mActionBarAdapter.getCurrentTab());
+ // force the tabs' underline to be cleared and redrawn
+ mViewPagerTabs.onPageScrolled(mActionBarAdapter.getCurrentTab(), 0, 0);
+ onResumeInit();
+ }
+ }
}
diff --git a/src/com/android/contacts/group/GroupDetailFragment.java b/src/com/android/contacts/group/GroupDetailFragment.java
index 724f37bf8..cb866c408 100644
--- a/src/com/android/contacts/group/GroupDetailFragment.java
+++ b/src/com/android/contacts/group/GroupDetailFragment.java
@@ -226,7 +226,7 @@ public class GroupDetailFragment extends Fragment implements OnScrollListener {
}
@Override
- public void onCallNumberDirectly(String phoneNumber) {
+ public void onCallNumberDirectly(String phoneNumber, String mimeType) {
// No need to call phone number directly from People app.
Log.w(TAG, "unexpected invocation of onCallNumberDirectly()");
}
diff --git a/src/com/android/contacts/incall/InCallPluginHelper.java b/src/com/android/contacts/incall/InCallPluginHelper.java
new file mode 100644
index 000000000..f7fe3b6c7
--- /dev/null
+++ b/src/com/android/contacts/incall/InCallPluginHelper.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 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 com.android.contacts.incall;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.phone.common.ambient.AmbientConnection;
+import com.android.phone.common.incall.CallMethodHelper;
+import com.android.phone.common.incall.CallMethodInfo;
+import com.cyanogen.ambient.common.api.ResultCallback;
+import com.cyanogen.ambient.discovery.util.NudgeKey;
+import com.cyanogen.ambient.incall.extension.InCallContactInfo;
+import com.cyanogen.ambient.incall.InCallServices;
+import com.cyanogen.ambient.incall.results.InstalledPluginsResult;
+
+public class InCallPluginHelper extends CallMethodHelper {
+ private static final String TAG = InCallPluginHelper.class.getSimpleName();
+
+ private static final int EXPECTED_CALL_BACKS = 11;
+
+ protected static synchronized InCallPluginHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new InCallPluginHelper();
+ }
+ return (InCallPluginHelper) sInstance;
+ }
+
+ public static void init(Context context) {
+ InCallPluginHelper helper = getInstance();
+ helper.expectedCallbacks = EXPECTED_CALL_BACKS;
+ helper.mContext = context;
+ helper.mClient = AmbientConnection.CLIENT.get(context);
+ helper.mInCallApi = InCallServices.getInstance();
+ helper.mMainHandler = new Handler(context.getMainLooper());
+
+ refresh();
+ }
+
+ public static void refresh() {
+ updateCallPlugins();
+ }
+
+ public static void refreshDynamicItems() {
+ for (ComponentName cn : mCallMethodInfos.keySet()) {
+ getCallMethodAuthenticated(cn, true);
+ }
+ }
+
+ public static void refreshPendingIntents(InCallContactInfo contactInfo) {
+ for (ComponentName cn : mCallMethodInfos.keySet()) {
+ getInviteIntent(cn, contactInfo);
+ getDirectorySearchIntent(cn, contactInfo.mLookupUri);
+ }
+ }
+
+ protected static void updateCallPlugins() {
+ if (DEBUG) Log.d(TAG, "+++updateCallPlugins");
+ getInstance().mInCallApi.getInstalledPlugins(getInstance().mClient)
+ .setResultCallback(new ResultCallback<InstalledPluginsResult>() {
+ @Override
+ public void onResult(InstalledPluginsResult installedPluginsResult) {
+ // got installed components
+ mInstalledPlugins = installedPluginsResult.components;
+
+ synchronized (mCallMethodInfos) {
+ mCallMethodInfos.clear();
+ }
+
+ if (mInstalledPlugins.size() == 0) {
+ broadcast(false);
+ }
+
+ for (ComponentName cn : mInstalledPlugins) {
+ mCallMethodInfos.put(cn, new CallMethodInfo());
+ getCallMethodInfo(cn);
+ getCallMethodMimeType(cn);
+ getCallMethodStatus(cn);
+ getCallMethodVideoCallableMimeType(cn);
+ getCallMethodImMimeType(cn);
+ getCallMethodAuthenticated(cn, false);
+ getLoginIntent(cn);
+ getNudgeConfiguration(cn, NudgeKey.INCALL_CONTACT_FRAGMENT_LOGIN);
+ getNudgeConfiguration(cn, NudgeKey.INCALL_CONTACT_CARD_LOGIN);
+ getNudgeConfiguration(cn, NudgeKey.INCALL_CONTACT_CARD_DOWNLOAD);
+ getDefaultDirectorySearchIntent(cn);
+ // If you add any more callbacks, be sure to update
+ // EXPECTED_RESULT_CALLBACKS
+ // and EXPECTED_DYNAMIC_RESULT_CALLBACKS if the callback is dynamic
+ // with the proper count.
+ }
+ }
+ });
+ }
+}
diff --git a/src/com/android/contacts/incall/InCallPluginInfo.java b/src/com/android/contacts/incall/InCallPluginInfo.java
new file mode 100644
index 000000000..26ba0fc2e
--- /dev/null
+++ b/src/com/android/contacts/incall/InCallPluginInfo.java
@@ -0,0 +1,102 @@
+package com.android.contacts.incall;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.list.PluginContactBrowseListFragment;
+import com.android.phone.common.incall.CallMethodInfo;
+
+import java.util.Objects;
+
+
+public class InCallPluginInfo implements Parcelable {
+ private static String TAG = InCallPluginInfo.class.getSimpleName();
+ private boolean DEBUG = false;
+
+ public CallMethodInfo mCallMethodInfo = new CallMethodInfo();
+ public String mTabTag; // uniquely identifies a ViewPagerTab and also serves as fragment ID
+ public PluginContactBrowseListFragment mFragment; // This reference will not be saved
+
+
+ public static final Parcelable.Creator<InCallPluginInfo> CREATOR
+ = new Parcelable.Creator<InCallPluginInfo>() {
+ public InCallPluginInfo createFromParcel(Parcel in) {
+ return new InCallPluginInfo(in);
+ }
+
+ public InCallPluginInfo[] newArray(int size) {
+ return new InCallPluginInfo[size];
+ }
+ };
+
+ public InCallPluginInfo() {}
+
+ private InCallPluginInfo(Parcel in) {
+ mCallMethodInfo.mAccountType = in.readString();
+ mCallMethodInfo.mAccountHandle = in.readString();
+ mCallMethodInfo.mIsAuthenticated = in.readInt() == 1;
+ mCallMethodInfo.mStatus = in.readInt();
+ mCallMethodInfo.mDirectorySearchIntent =
+ in.readParcelable(PendingIntent.class.getClassLoader());
+ mCallMethodInfo.mLoginIntent = in.readParcelable(PendingIntent.class.getClassLoader());
+ mCallMethodInfo.mName = in.readString();
+ mCallMethodInfo.mBrandIconId = in.readInt();
+ mCallMethodInfo.mLoginIconId = in.readInt();
+ mTabTag = in.readString();
+ mCallMethodInfo.mComponent = in.readParcelable(ComponentName.class.getClassLoader());
+ mCallMethodInfo.mNudgeComponent = in.readParcelable(ComponentName.class.getClassLoader());
+ mCallMethodInfo.mDependentPackage = in.readString();
+ mCallMethodInfo.mMimeType = in.readString();
+ mCallMethodInfo.mImMimeType = in.readString();
+ mCallMethodInfo.mVideoCallableMimeType = in.readString();
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mCallMethodInfo.mAccountType);
+ dest.writeString(mCallMethodInfo.mAccountHandle);
+ dest.writeInt(mCallMethodInfo.mIsAuthenticated ? 1 : 0);
+ dest.writeInt(mCallMethodInfo.mStatus);
+ dest.writeParcelable(mCallMethodInfo.mDirectorySearchIntent, flags);
+ dest.writeParcelable(mCallMethodInfo.mLoginIntent, flags);
+ dest.writeString(mCallMethodInfo.mName);
+ dest.writeInt(mCallMethodInfo.mBrandIconId);
+ dest.writeInt(mCallMethodInfo.mLoginIconId);
+ dest.writeString(mTabTag);
+ dest.writeString(mTabTag);
+ dest.writeParcelable(mCallMethodInfo.mComponent, flags);
+ dest.writeParcelable(mCallMethodInfo.mNudgeComponent, flags);
+ dest.writeString(mCallMethodInfo.mDependentPackage);
+ dest.writeString(mCallMethodInfo.mMimeType);
+ dest.writeString(mCallMethodInfo.mImMimeType);
+ dest.writeString(mCallMethodInfo.mVideoCallableMimeType);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null) {
+ return false;
+ }
+ if (!(other instanceof InCallPluginInfo)) {
+ return false;
+ }
+ InCallPluginInfo otherObj = (InCallPluginInfo) other;
+ return this.mCallMethodInfo.equals(otherObj.mCallMethodInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ return mTabTag.hashCode();
+ }
+}
diff --git a/src/com/android/contacts/incall/InCallPluginUtils.java b/src/com/android/contacts/incall/InCallPluginUtils.java
new file mode 100644
index 000000000..2b8a15f82
--- /dev/null
+++ b/src/com/android/contacts/incall/InCallPluginUtils.java
@@ -0,0 +1,201 @@
+package com.android.contacts.incall;
+
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.contacts.common.ContactPresenceIconUtil;
+import com.android.contacts.common.ContactStatusUtil;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.Contact;
+import com.android.contacts.common.model.RawContact;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.model.dataitem.DataItem;
+import com.android.contacts.common.util.DataStatus;
+import com.android.contacts.common.util.UriUtils;
+import com.android.phone.common.ambient.AmbientConnection;
+import com.android.phone.common.incall.CallMethodHelper;
+import com.android.phone.common.incall.CallMethodInfo;
+import com.android.phone.common.util.StartInCallCallReceiver;
+import com.cyanogen.ambient.common.api.AmbientApiClient;
+import com.cyanogen.ambient.incall.InCallServices;
+import com.cyanogen.ambient.incall.extension.InCallContactInfo;
+import com.cyanogen.ambient.incall.extension.OriginCodes;
+import com.cyanogen.ambient.incall.extension.StartCallRequest;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+
+public class InCallPluginUtils {
+ private static final String TAG = InCallPluginUtils.class.getSimpleName();
+ private static boolean DEBUG = false;
+
+ // key to store data id for InCall plugin callable entries
+ public static final String KEY_DATA_ID = InCallPluginUtils.class.getPackage().getName() +
+ ".data_id";
+ public static final String KEY_MIMETYPE = InCallPluginUtils.class.getPackage().getName() +
+ ".mimetype";
+ public static final String KEY_COMPONENT = InCallPluginUtils.class.getPackage().getName() +
+ ".component";
+ public static final String KEY_NAME = InCallPluginUtils.class.getPackage().getName() + ".name";
+ public static final String KEY_NUMBER = InCallPluginUtils.class.getPackage().getName() +
+ ".number";
+ public static final String KEY_NUDGE_KEY = InCallPluginUtils.class.getPackage().getName() + ""
+ + ".nudge_key";
+
+ public static Drawable getDrawable(Context context, int resourceId,
+ ComponentName componentName) {
+ Resources pluginRes = null;
+ Drawable drawable = null;
+
+ if (resourceId == 0) {
+ return null;
+ }
+ try {
+ pluginRes = context.getPackageManager().getResourcesForApplication(
+ componentName.getPackageName());
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Plugin not installed: " + componentName, e);
+ return null;
+ }
+ try {
+ drawable = pluginRes.getDrawable(resourceId);
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Plugin does not define login icon: " + componentName, e);
+ }
+ return drawable;
+ }
+
+ public static Intent getVoiceMimeIntent(String mimeType, DataItem dataItem, CallMethodInfo cmi,
+ String contactAccountHandle) {
+ Intent intent = new Intent();
+ intent.putExtra(KEY_DATA_ID, dataItem.getId());
+ intent.putExtra(KEY_MIMETYPE, mimeType);
+ intent.putExtra(KEY_COMPONENT, cmi.mComponent.flattenToString());
+ intent.putExtra(KEY_NAME, cmi.mName);
+ intent.putExtra(KEY_NUMBER, contactAccountHandle);
+ return intent;
+ }
+
+ public static Intent getImMimeIntent(String mimeType, RawContact rawContact) {
+ DataItem imDataItem = null;
+ long dataId;
+ for (DataItem dataItem : rawContact.getDataItems()) {
+ if (dataItem.getMimeType().equals(mimeType)) {
+ imDataItem = dataItem;
+ break;
+ }
+ }
+ dataId = imDataItem == null ? -1 : imDataItem.getId();
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.putExtra(KEY_DATA_ID, dataId);
+ intent.setDataAndType(ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI,
+ dataId), mimeType);
+ return intent;
+ }
+
+ public static class PresenceInfo {
+ public Drawable mPresenceIcon;
+ public String mStatusMsg;
+ }
+ // targetRawContact is the raw contact with the same account type as the InCall plugin
+ public static PresenceInfo lookupPresenceInfo(Context context, Contact contact, RawContact
+ targetRawContact) {
+ // When ContactLoader loads a list of raw contact in the ImmutableList, each raw contact's
+ // associated presence data is placed in the ImmutableMap in the same order. ImmutableMap
+ // iterates in the same order items were originally placed in it.
+ PresenceInfo presenceInfo = new PresenceInfo();
+ ImmutableList<RawContact> rawContacts = contact.getRawContacts();
+ ImmutableMap<Long, DataStatus> statusMap = contact.getStatuses();
+ if (rawContacts == null || statusMap == null) {
+ return presenceInfo;
+ }
+ // Look for the position of the raw contact that corresponds to the plugin
+ int index = rawContacts.indexOf(targetRawContact);
+ if (index < 0 || rawContacts.size() != statusMap.size()) {
+ // target raw contact entry not found or the list and map sizes mismatch (we may risk
+ // the iterator not finding the matching presence data entry)
+ return presenceInfo;
+ }
+ // move the iterator to the same offset as the raw contact to retrieve the presence data
+ Iterator it = statusMap.entrySet().iterator();
+ ImmutableMap.Entry<Long, DataStatus> mapEntry = null;
+ while (it.hasNext()) {
+ if (index == 0) {
+ mapEntry = (ImmutableMap.Entry<Long, DataStatus>)it.next();
+ break;
+ }
+ index--;
+ }
+ if (mapEntry == null) {
+ return presenceInfo;
+ }
+ DataStatus presenceStatus = mapEntry.getValue();
+ int presence = presenceStatus == null ? ContactsContract.StatusUpdates.OFFLINE :
+ presenceStatus.getPresence();
+ presenceInfo.mPresenceIcon = ContactPresenceIconUtil.getPresenceIcon(context, presence);
+ presenceInfo.mStatusMsg = ContactStatusUtil.getStatusString(context, presence);
+ return presenceInfo;
+ }
+
+ public static ArrayList<ComponentName> gatherPluginHistory() {
+ ArrayList<ComponentName> cnList = new ArrayList<ComponentName>();
+ HashMap<ComponentName, CallMethodInfo> plugins = InCallPluginHelper
+ .getAllEnabledCallMethods();
+ for (ComponentName cn : plugins.keySet()) {
+ cnList.add(cn);
+ }
+ return cnList;
+ }
+
+ // Retrieve the first contact data entry
+ public static String lookupContactData(Contact contact, String dataMimeType, String dataKind) {
+ String dataValue = "";
+
+ if (TextUtils.isEmpty(dataMimeType) || TextUtils.isEmpty(dataKind)) {
+ return null;
+ }
+ for (RawContact raw: contact.getRawContacts()) {
+ for (DataItem dataItem : raw.getDataItems()) {
+ final ContentValues entryValues = dataItem.getContentValues();
+ final String mimeType = dataItem.getMimeType();
+
+ if (mimeType == null) continue;
+
+ if (mimeType.equals(dataMimeType)) {
+ return entryValues.getAsString(dataKind);
+ }
+ }
+ }
+ return dataValue;
+ }
+
+ public static InCallContactInfo getInCallContactInfo(Contact contact) {
+ String phoneNumber = InCallPluginUtils.lookupContactData(contact,
+ ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
+ ContactsContract.CommonDataKinds.Phone.NUMBER);
+ Uri lookupUri = contact.getLookupUri();
+ if (UriUtils.isEncodedContactUri(lookupUri)) {
+ lookupUri = Uri.parse(TextUtils.isEmpty(contact.getDisplayName()) ? phoneNumber :
+ contact.getDisplayName());
+ }
+ return new InCallContactInfo(contact.getDisplayName(),
+ phoneNumber, lookupUri);
+
+ }
+}
diff --git a/src/com/android/contacts/interactions/CallLogInteraction.java b/src/com/android/contacts/interactions/CallLogInteraction.java
index 3464c0f95..4dd3e366b 100644
--- a/src/com/android/contacts/interactions/CallLogInteraction.java
+++ b/src/com/android/contacts/interactions/CallLogInteraction.java
@@ -16,9 +16,13 @@
package com.android.contacts.interactions;
import com.android.contacts.R;
+import com.android.contacts.common.CallUtil;
import com.android.contacts.common.util.BitmapUtil;
import com.android.contacts.common.util.ContactDisplayUtils;
+import com.android.contacts.incall.InCallPluginUtils;
+import com.android.contacts.quickcontact.QuickContactActivity;
+import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@@ -27,11 +31,16 @@ import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.CallLog.Calls;
+import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.telephony.PhoneNumberUtils;
import android.text.BidiFormatter;
import android.text.Spannable;
import android.text.TextDirectionHeuristics;
+import android.text.TextUtils;
+import com.android.contacts.incall.InCallPluginUtils;
+import com.cyanogen.ambient.incall.CallLogConstants;
/**
* Represents a call log event interaction, wrapping the columns in
* {@link android.provider.CallLog.Calls}.
@@ -52,6 +61,9 @@ public class CallLogInteraction implements ContactInteraction {
private static BidiFormatter sBidiFormatter = BidiFormatter.getInstance();
private ContentValues mValues;
+ private Drawable mIcon;
+ private int mIconResourceId = 0;
+ private String mPluginName;
public CallLogInteraction(ContentValues values) {
mValues = values;
@@ -60,8 +72,22 @@ public class CallLogInteraction implements ContactInteraction {
@Override
public Intent getIntent() {
String number = getNumber();
- return number == null ? null : new Intent(Intent.ACTION_CALL).setData(
- Uri.parse(URI_TARGET_PREFIX + number));
+ Intent intent;
+ if (TextUtils.isEmpty(getPluginPkgName())) {
+ // regular PSTN
+ intent = CallUtil.getCallIntent(getNumber());
+ } else {
+ // plugin
+ intent = new Intent();
+ intent.putExtra(InCallPluginUtils.KEY_DATA_ID,
+ QuickContactActivity.CARD_ENTRY_ID_INCALL_PLUGIN_CALL);
+ intent.putExtra(InCallPluginUtils.KEY_COMPONENT, getPluginPkgName());
+ intent.putExtra(InCallPluginUtils.KEY_NAME, getPluginName());
+ intent.putExtra(InCallPluginUtils.KEY_NUMBER, number);
+ intent.putExtra(InCallPluginUtils.KEY_MIMETYPE, PhoneNumberUtils.isGlobalPhoneNumber
+ (number) ? ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE : "");
+ }
+ return intent;
}
@Override
@@ -94,7 +120,12 @@ public class CallLogInteraction implements ContactInteraction {
@Override
public Drawable getIcon(Context context) {
- return context.getResources().getDrawable(CALL_LOG_ICON_RES);
+ if (mIcon != null) {
+ // it's a plugin interaction
+ return mIcon;
+ } else {
+ return context.getResources().getDrawable(CALL_LOG_ICON_RES);
+ }
}
@Override
@@ -212,6 +243,29 @@ public class CallLogInteraction implements ContactInteraction {
@Override
public int getIconResourceId() {
- return CALL_LOG_ICON_RES;
+ if (mIconResourceId != 0) {
+ return mIconResourceId;
+ } else {
+ return CALL_LOG_ICON_RES;
+ }
+ }
+
+ public void setPluginInfo(Context context, int resourceId, String pluginName) {
+ ComponentName compName = ComponentName.unflattenFromString(getPluginPkgName());
+ mIcon = InCallPluginUtils.getDrawable(context, resourceId, compName);
+ mIconResourceId = resourceId;
+ mPluginName = pluginName;
+ }
+
+ public String getPluginPkgName() {
+ return mValues.getAsString(CallLogConstants.PLUGIN_PACKAGE_NAME);
+ }
+
+ public String getPluginUserHandle() {
+ return mValues.getAsString(CallLogConstants.PLUGIN_USER_HANDLE);
+ }
+
+ public String getPluginName() {
+ return mPluginName;
}
}
diff --git a/src/com/android/contacts/interactions/CallLogInteractionsLoader.java b/src/com/android/contacts/interactions/CallLogInteractionsLoader.java
index 2340d3f29..c64421c84 100644
--- a/src/com/android/contacts/interactions/CallLogInteractionsLoader.java
+++ b/src/com/android/contacts/interactions/CallLogInteractionsLoader.java
@@ -16,6 +16,7 @@
package com.android.contacts.interactions;
import android.content.AsyncTaskLoader;
+import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -26,25 +27,31 @@ import android.provider.CallLog.Calls;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import com.android.contacts.incall.InCallPluginHelper;
import com.google.common.annotations.VisibleForTesting;
import com.android.contacts.common.util.PermissionsUtil;
+import com.android.phone.common.incall.CallMethodInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
public class CallLogInteractionsLoader extends AsyncTaskLoader<List<ContactInteraction>> {
-
+ private final Context mContext;
private final String[] mPhoneNumbers;
private final int mMaxToRetrieve;
private List<ContactInteraction> mData;
+ private HashMap<ComponentName, List<String>> mPluginAccountsMap;
- public CallLogInteractionsLoader(Context context, String[] phoneNumbers,
- int maxToRetrieve) {
+ public CallLogInteractionsLoader(Context context, String[] phoneNumbers, HashMap<ComponentName,
+ List<String>> pluginAccountsMap, int maxToRetrieve) {
super(context);
+ mContext = context;
mPhoneNumbers = phoneNumbers;
+ mPluginAccountsMap = pluginAccountsMap;
mMaxToRetrieve = maxToRetrieve;
}
@@ -53,13 +60,31 @@ public class CallLogInteractionsLoader extends AsyncTaskLoader<List<ContactInter
if (!PermissionsUtil.hasPhonePermissions(getContext())
|| !getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- || mPhoneNumbers == null || mPhoneNumbers.length <= 0 || mMaxToRetrieve <= 0) {
+ || (mPhoneNumbers == null || mPhoneNumbers.length <= 0 || mMaxToRetrieve <= 0)) {
return Collections.emptyList();
}
final List<ContactInteraction> interactions = new ArrayList<>();
- for (String number : mPhoneNumbers) {
- interactions.addAll(getCallLogInteractions(number));
+ if (mPhoneNumbers != null) {
+ for (String number : mPhoneNumbers) {
+ interactions.addAll(getCallLogInteractions(number, null));
+ }
+ }
+ // add plugin entries
+ if (InCallPluginHelper.infoReady()) {
+ HashMap<ComponentName, CallMethodInfo> inCallPlugins = InCallPluginHelper
+ .getAllEnabledCallMethods();
+ if (inCallPlugins != null) {
+ for (ComponentName cn : inCallPlugins.keySet()) {
+ List<String> accountList = mPluginAccountsMap.get(cn);
+ CallMethodInfo cmi = inCallPlugins.get(cn);
+ if (cmi == null) continue;
+ if (accountList == null || cmi == null) continue;
+ for (int i = 0; i < accountList.size(); i++) {
+ interactions.addAll(getCallLogInteractions(accountList.get(i), cmi));
+ }
+ }
+ }
}
// Sort the call log interactions by date for duplicate removal
Collections.sort(interactions, new Comparator<ContactInteraction>() {
@@ -75,7 +100,7 @@ public class CallLogInteractionsLoader extends AsyncTaskLoader<List<ContactInter
}
});
// Duplicates only occur because of fuzzy matching. No need to dedupe a single number.
- if (mPhoneNumbers.length == 1) {
+ if (interactions.size() == 1) {
return interactions;
}
return pruneDuplicateCallLogInteractions(interactions, mMaxToRetrieve);
@@ -106,10 +131,13 @@ public class CallLogInteractionsLoader extends AsyncTaskLoader<List<ContactInter
return subsetInteractions;
}
- private List<ContactInteraction> getCallLogInteractions(String phoneNumber) {
+ private List<ContactInteraction> getCallLogInteractions(String phoneNumber, CallMethodInfo
+ cmi) {
// TODO: the phone number added to the ContactInteractions result should retain their
// original formatting since TalkBack is not reading the normalized number correctly
- final String normalizedNumber = PhoneNumberUtils.normalizeNumber(phoneNumber);
+ String pluginComponent = cmi == null ? "" : cmi.mComponent.flattenToString();
+ final String normalizedNumber = TextUtils.isEmpty(pluginComponent) ?
+ PhoneNumberUtils.normalizeNumber(phoneNumber) : phoneNumber;
// If the number contains only symbols, we can skip it
if (TextUtils.isEmpty(normalizedNumber)) {
return Collections.emptyList();
@@ -131,7 +159,27 @@ public class CallLogInteractionsLoader extends AsyncTaskLoader<List<ContactInter
while (cursor.moveToNext()) {
final ContentValues values = new ContentValues();
DatabaseUtils.cursorRowToContentValues(cursor, values);
- interactions.add(new CallLogInteraction(values));
+ CallLogInteraction interaction = new CallLogInteraction(values);
+ // loadInBackground calls this function twice
+ // First pass: argument phoneNumber: PSTN number, pluginComponent: null
+ // (if the PSTN number was dialed through a plugin, the queried cursor entry should
+ // contain the plugin component in the "plugin_package_name" column)
+ // Second pass: argument phoneNumber: plugin user handle, pluginComponent: valid
+ if ((TextUtils.isEmpty(pluginComponent) &&
+ !TextUtils.isEmpty(interaction.getPluginPkgName())) ||
+ (!TextUtils.isEmpty(pluginComponent) &&
+ TextUtils.equals(interaction.getPluginPkgName(), pluginComponent)))
+ {
+ // PSTN dialed through a plugin
+ if (cmi == null) {
+ cmi = InCallPluginHelper.getCallMethod(ComponentName.unflattenFromString
+ (interaction.getPluginPkgName()));
+ }
+ // No matching plugin, skip
+ if (cmi == null) continue;
+ interaction.setPluginInfo(mContext, cmi.mBrandIconId, cmi.mName);
+ }
+ interactions.add(interaction);
}
return interactions;
} finally {
diff --git a/src/com/android/contacts/list/ContactTileListFragment.java b/src/com/android/contacts/list/ContactTileListFragment.java
index 189cfd375..33506f200 100644
--- a/src/com/android/contacts/list/ContactTileListFragment.java
+++ b/src/com/android/contacts/list/ContactTileListFragment.java
@@ -232,7 +232,7 @@ public class ContactTileListFragment extends Fragment {
}
@Override
- public void onCallNumberDirectly(String phoneNumber) {
+ public void onCallNumberDirectly(String phoneNumber, String mimeType) {
if (mListener != null) {
mListener.onCallNumberDirectly(phoneNumber);
}
diff --git a/src/com/android/contacts/list/PluginContactBrowseListFragment.java b/src/com/android/contacts/list/PluginContactBrowseListFragment.java
new file mode 100644
index 000000000..3a5aeaa6e
--- /dev/null
+++ b/src/com/android/contacts/list/PluginContactBrowseListFragment.java
@@ -0,0 +1,765 @@
+/*
+ * Copyright (C) 2010 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.list;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.*;
+import android.provider.ContactsContract;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.common.widget.CompositeCursorAdapter;
+import com.android.contacts.common.list.AutoScrollListView;
+import com.android.contacts.common.list.ContactEntryListFragment;
+import com.android.contacts.common.list.DirectoryPartition;
+import com.android.contacts.common.util.ContactLoaderUtils;
+import com.android.contacts.incall.InCallPluginInfo;
+import com.android.contacts.R;
+import com.android.contacts.common.list.ContactListAdapter;
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.list.ContactListItemView;
+import com.android.contacts.common.list.DefaultContactListAdapter;
+import com.android.contacts.common.list.ProfileAndContactsLoader;
+import com.android.contacts.incall.InCallPluginUtils;
+
+import java.util.List;
+
+public class PluginContactBrowseListFragment extends ContactEntryListFragment<ContactListAdapter>
+ implements View.OnClickListener {
+ private static final String TAG = PluginContactBrowseListFragment.class.getSimpleName();
+ private boolean DEBUG = false;
+ private Context mContext;
+ private InCallPluginInfo mInCallPluginInfo;
+ private View mLayout;
+ private View mListView;
+ private View mLoginView;
+ private Button mLoginBtn;
+ private ImageView mLoginIconView;
+ private TextView mLoginMsg;
+ private TextView mEmptyView;
+ private static final String KEY_SELECTED_URI = "selectedUri";
+ private static final String KEY_SELECTION_VERIFIED = "selectionVerified";
+ private static final String KEY_FILTER = "filter";
+ private static final String KEY_LAST_SELECTED_POSITION = "lastSelected";
+ private static final String KEY_SHARED_PREFS_FILE_NAME = "prefsFileName";
+
+ private static final String PERSISTENT_SELECTION_PREFIX = "defaultContactBrowserSelection";
+ private static final String PREFS_FILE_PREFIX = "plugin.";
+ private SharedPreferences mPrefs;
+ private String mPrefsFileName;
+
+ private boolean mStartedLoading;
+ private boolean mSelectionToScreenRequested;
+ private boolean mSmoothScrollRequested;
+ private boolean mSelectionPersistenceRequested;
+ private Uri mSelectedContactUri;
+ private long mSelectedContactDirectoryId;
+ private String mSelectedContactLookupKey;
+ private long mSelectedContactId;
+ private boolean mSelectionVerified;
+ private int mLastSelectedPosition = -1;
+ private boolean mRefreshingContactUri;
+ private ContactListFilter mFilter;
+ private String mPersistentSelectionPrefix = PERSISTENT_SELECTION_PREFIX;
+
+ protected OnContactBrowserActionListener mListener;
+ private ContactLookupTask mContactLookupTask;
+
+ private final class ContactLookupTask extends AsyncTask<Void, Void, Uri> {
+
+ private final Uri mUri;
+ private boolean mIsCancelled;
+
+ public ContactLookupTask(Uri uri) {
+ mUri = uri;
+ }
+
+ @Override
+ protected Uri doInBackground(Void... args) {
+ Cursor cursor = null;
+ try {
+ final ContentResolver resolver = getContext().getContentResolver();
+ final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(resolver, mUri);
+ cursor = resolver.query(uriCurrentFormat,
+ new String[] { ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.LOOKUP_KEY }, null, null, null);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ final long contactId = cursor.getLong(0);
+ final String lookupKey = cursor.getString(1);
+ if (contactId != 0 && !TextUtils.isEmpty(lookupKey)) {
+ return ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
+ }
+ }
+
+ Log.e(TAG, "Error: No contact ID or lookup key for contact " + mUri);
+ return null;
+ } catch (Exception e) {
+ Log.e(TAG, "Error loading the contact: " + mUri, e);
+ return null;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ public void cancel() {
+ super.cancel(true);
+ // Use a flag to keep track of whether the {@link AsyncTask} was cancelled or not in
+ // order to ensure onPostExecute() is not executed after the cancel request. The flag is
+ // necessary because {@link AsyncTask} still calls onPostExecute() if the cancel request
+ // came after the worker thread was finished.
+ mIsCancelled = true;
+ }
+
+ @Override
+ protected void onPostExecute(Uri uri) {
+ // Make sure the {@link Fragment} is at least still attached to the {@link Activity}
+ // before continuing. Null URIs should still be allowed so that the list can be
+ // refreshed and a default contact can be selected (i.e. the case of deleted
+ // contacts).
+ if (mIsCancelled || !isAdded()) {
+ return;
+ }
+ onContactUriQueryFinished(uri);
+ }
+ }
+
+ public PluginContactBrowseListFragment() {
+ if (DEBUG) Log.d(TAG, "Constructor");
+ setPhotoLoaderEnabled(true);
+ setQuickContactEnabled(false);
+ setSectionHeaderDisplayEnabled(true);
+ setVisibleScrollbarEnabled(true);
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ if (DEBUG) Log.d(TAG, "onAttach");
+ }
+
+ // This method is called in parent class of onCreateView
+ @Override
+ protected View inflateView(LayoutInflater inflater, ViewGroup container) {
+ mLayout = inflater.inflate(R.layout.plugin_contact_list_content, null);
+ return mLayout;
+ }
+
+ @Override
+ protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
+ if (DEBUG) Log.d(TAG, "onCreateView");
+ super.onCreateView(inflater, container);
+ mLoginView = mLayout.findViewById(R.id.plugin_login_layout);
+ mListView = mLayout.findViewById(android.R.id.list);
+ mLoginBtn = (Button) mLayout.findViewById(R.id.plugin_login_button);
+ mLoginBtn.setOnClickListener(this);
+ mLoginIconView = (ImageView) mLayout.findViewById(R.id.plugin_login_icon);
+ mLoginMsg = (TextView) mLayout.findViewById(R.id.plugin_login_msg);
+ mEmptyView = (TextView) mLayout.findViewById(android.R.id.empty);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ if (DEBUG) Log.d(TAG, "onActivityCreated");
+ super.onActivityCreated(savedInstanceState);
+ mPrefs = getActivity().getSharedPreferences(mPrefsFileName, Context.MODE_PRIVATE);
+ updatePluginView(); // this is execute again reflect change in plugin info
+ restoreFilter();
+ restoreSelectedUri(false);
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ final SharedPreferences oldFragPrefs = getActivity().getSharedPreferences(mPrefsFileName,
+ Context.MODE_PRIVATE);
+ oldFragPrefs.edit().clear().commit();
+ }
+
+ public void setFilter(ContactListFilter filter) {
+ setFilter(filter, true);
+ }
+
+ public void setFilter(ContactListFilter filter, boolean restoreSelectedUri) {
+ if (mFilter == null && filter == null) {
+ return;
+ }
+
+ if (mFilter != null && mFilter.equals(filter)) {
+ return;
+ }
+
+ if (DEBUG) Log.v(TAG, "New filter: " + filter);
+
+ mFilter = filter;
+ mLastSelectedPosition = -1;
+ saveFilter();
+ if (restoreSelectedUri) {
+ mSelectedContactUri = null;
+ restoreSelectedUri(true);
+ }
+ reloadData();
+ }
+
+ public ContactListFilter getFilter() {
+ return mFilter;
+ }
+
+ @Override
+ public void restoreSavedState(Bundle savedState) {
+ super.restoreSavedState(savedState);
+ if (savedState == null) {
+ if (DEBUG) Log.d(TAG, "restoreSavedState null");
+ return;
+ }
+
+ mFilter = savedState.getParcelable(KEY_FILTER);
+ mSelectedContactUri = savedState.getParcelable(KEY_SELECTED_URI);
+ mSelectionVerified = savedState.getBoolean(KEY_SELECTION_VERIFIED);
+ mLastSelectedPosition = savedState.getInt(KEY_LAST_SELECTED_POSITION);
+ mPrefsFileName = savedState.getString(KEY_SHARED_PREFS_FILE_NAME, "");
+ if (DEBUG) Log.d(TAG, "restoreSavedState mPrefsFileName :" + mPrefsFileName);
+ if (!mPrefsFileName.equals("")) {
+ mPrefs = getActivity().getSharedPreferences(mPrefsFileName, Context.MODE_PRIVATE);
+ }
+ parseSelectedContactUri();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelable(KEY_FILTER, mFilter);
+ outState.putParcelable(KEY_SELECTED_URI, mSelectedContactUri);
+ outState.putBoolean(KEY_SELECTION_VERIFIED, mSelectionVerified);
+ outState.putInt(KEY_LAST_SELECTED_POSITION, mLastSelectedPosition);
+ outState.putString(KEY_SHARED_PREFS_FILE_NAME, mPrefsFileName);
+ }
+
+ protected void refreshSelectedContactUri() {
+ if (mContactLookupTask != null) {
+ mContactLookupTask.cancel();
+ }
+
+ if (!isSelectionVisible()) {
+ return;
+ }
+
+ mRefreshingContactUri = true;
+
+ if (mSelectedContactUri == null) {
+ onContactUriQueryFinished(null);
+ return;
+ }
+
+ if (mSelectedContactDirectoryId != ContactsContract.Directory.DEFAULT
+ && mSelectedContactDirectoryId != ContactsContract.Directory.LOCAL_INVISIBLE) {
+ onContactUriQueryFinished(mSelectedContactUri);
+ } else {
+ mContactLookupTask = new ContactLookupTask(mSelectedContactUri);
+ mContactLookupTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
+ }
+ }
+
+ protected void onContactUriQueryFinished(Uri uri) {
+ mRefreshingContactUri = false;
+ mSelectedContactUri = uri;
+ parseSelectedContactUri();
+ checkSelection();
+ }
+
+ public InCallPluginInfo getPluginInfo() {
+ return mInCallPluginInfo;
+ }
+
+ public Uri getSelectedContactUri() {
+ return mSelectedContactUri;
+ }
+
+ /**
+ * Sets the new selection for the list.
+ */
+ public void setSelectedContactUri(Uri uri) {
+ setSelectedContactUri(uri, true, false /* no smooth scroll */, true, false);
+ }
+
+ /**
+ * Sets the new contact selection.
+ *
+ * @param uri the new selection
+ * @param required if true, we need to check if the selection is present in
+ * the list and if not notify the listener so that it can load a
+ * different list
+ * @param smoothScroll if true, the UI will roll smoothly to the new
+ * selection
+ * @param persistent if true, the selection will be stored in shared
+ * preferences.
+ * @param willReloadData if true, the selection will be remembered but not
+ * actually shown, because we are expecting that the data will be
+ * reloaded momentarily
+ */
+ private void setSelectedContactUri(Uri uri, boolean required, boolean smoothScroll,
+ boolean persistent, boolean willReloadData) {
+ mSmoothScrollRequested = smoothScroll;
+ mSelectionToScreenRequested = true;
+
+ if ((mSelectedContactUri == null && uri != null)
+ || (mSelectedContactUri != null && !mSelectedContactUri.equals(uri))) {
+ mSelectionVerified = false;
+ mSelectionPersistenceRequested = persistent;
+ mSelectedContactUri = uri;
+ parseSelectedContactUri();
+
+ if (!willReloadData) {
+ // Configure the adapter to show the selection based on the
+ // lookup key extracted from the URI
+ ContactListAdapter adapter = getAdapter();
+ if (adapter != null) {
+ adapter.setSelectedContact(mSelectedContactDirectoryId,
+ mSelectedContactLookupKey, mSelectedContactId);
+ getListView().invalidateViews();
+ }
+ }
+
+ // Also, launch a loader to pick up a new lookup URI in case it has changed
+ refreshSelectedContactUri();
+ }
+ }
+
+ private void parseSelectedContactUri() {
+ if (mSelectedContactUri != null) {
+ String directoryParam =
+ mSelectedContactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
+ mSelectedContactDirectoryId = TextUtils.isEmpty(directoryParam) ?
+ ContactsContract.Directory.DEFAULT : Long.parseLong(directoryParam);
+ if (mSelectedContactUri.toString().startsWith(
+ ContactsContract.Contacts.CONTENT_LOOKUP_URI.toString())) {
+ List<String> pathSegments = mSelectedContactUri.getPathSegments();
+ mSelectedContactLookupKey = Uri.encode(pathSegments.get(2));
+ if (pathSegments.size() == 4) {
+ mSelectedContactId = ContentUris.parseId(mSelectedContactUri);
+ }
+ } else if (mSelectedContactUri.toString().startsWith(
+ ContactsContract.Contacts.CONTENT_URI.toString()) &&
+ mSelectedContactUri.getPathSegments().size() >= 2) {
+ mSelectedContactLookupKey = null;
+ mSelectedContactId = ContentUris.parseId(mSelectedContactUri);
+ } else {
+ Log.e(TAG, "Unsupported contact URI: " + mSelectedContactUri);
+ mSelectedContactLookupKey = null;
+ mSelectedContactId = 0;
+ }
+
+ } else {
+ mSelectedContactDirectoryId = ContactsContract.Directory.DEFAULT;
+ mSelectedContactLookupKey = null;
+ mSelectedContactId = 0;
+ }
+ }
+
+ @Override
+ protected void configureAdapter() {
+ super.configureAdapter();
+
+ ContactListAdapter adapter = getAdapter();
+ if (adapter == null) {
+ return;
+ }
+
+ if (mFilter != null) {
+ adapter.setFilter(mFilter);
+ }
+
+ adapter.setIncludeProfile(false); // TODO: do not need profile
+ }
+
+ @Override
+ public CursorLoader createCursorLoader(Context context) {
+ return new ProfileAndContactsLoader(context);
+ }
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ super.onLoadFinished(loader, data);
+ mSelectionVerified = false;
+
+ // Refresh the currently selected lookup in case it changed while we were sleeping
+ refreshSelectedContactUri();
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ super.onLoaderReset(loader);
+ }
+
+ private void checkSelection() {
+ if (mSelectionVerified) {
+ return;
+ }
+
+ if (mRefreshingContactUri) {
+ return;
+ }
+
+ if (isLoadingDirectoryList()) {
+ return;
+ }
+
+ ContactListAdapter adapter = getAdapter();
+ if (adapter == null) {
+ return;
+ }
+
+ boolean directoryLoading = true;
+ int count = adapter.getPartitionCount();
+ for (int i = 0; i < count; i++) {
+ CompositeCursorAdapter.Partition partition = adapter.getPartition(i);
+ if (partition instanceof DirectoryPartition) {
+ DirectoryPartition directory = (DirectoryPartition) partition;
+ if (directory.getDirectoryId() == mSelectedContactDirectoryId) {
+ directoryLoading = directory.isLoading();
+ break;
+ }
+ }
+ }
+
+ if (directoryLoading) {
+ return;
+ }
+
+ adapter.setSelectedContact(
+ mSelectedContactDirectoryId, mSelectedContactLookupKey, mSelectedContactId);
+
+ final int selectedPosition = adapter.getSelectedContactPosition();
+ if (selectedPosition != -1) {
+ mLastSelectedPosition = selectedPosition;
+ } else {
+ saveSelectedUri(null);
+ selectDefaultContact();
+ }
+
+ mSelectionVerified = true;
+
+ if (mSelectionPersistenceRequested) {
+ saveSelectedUri(mSelectedContactUri);
+ mSelectionPersistenceRequested = false;
+ }
+
+ if (mSelectionToScreenRequested) {
+ requestSelectionToScreen(selectedPosition);
+ }
+
+ getListView().invalidateViews();
+
+ if (mListener != null) {
+ mListener.onSelectionChange();
+ }
+ }
+
+ protected void selectDefaultContact() {
+ Uri contactUri = null;
+ ContactListAdapter adapter = getAdapter();
+ if (mLastSelectedPosition != -1) {
+ int count = adapter.getCount();
+ int pos = mLastSelectedPosition;
+ if (pos >= count && count > 0) {
+ pos = count - 1;
+ }
+ contactUri = adapter.getContactUri(pos);
+ }
+
+ if (contactUri == null) {
+ contactUri = adapter.getFirstContactUri();
+ }
+
+ setSelectedContactUri(contactUri, false, mSmoothScrollRequested, false, false);
+ }
+
+ protected void requestSelectionToScreen(int selectedPosition) {
+ if (selectedPosition != -1) {
+ AutoScrollListView listView = (AutoScrollListView)getListView();
+ listView.requestPositionToScreen(
+ selectedPosition + listView.getHeaderViewsCount(), mSmoothScrollRequested);
+ mSelectionToScreenRequested = false;
+ }
+ }
+
+ @Override
+ public boolean isLoading() {
+ return mRefreshingContactUri || super.isLoading();
+ }
+
+ @Override
+ protected void startLoading() {
+ mStartedLoading = true;
+ mSelectionVerified = false;
+ super.startLoading();
+ }
+
+ public void reloadDataAndSetSelectedUri(Uri uri) {
+ setSelectedContactUri(uri, true, true, true, true);
+ reloadData();
+ }
+
+ @Override
+ public void reloadData() {
+ if (mStartedLoading) {
+ mSelectionVerified = false;
+ mLastSelectedPosition = -1;
+ super.reloadData();
+ }
+ }
+
+ public void setOnContactListActionListener(OnContactBrowserActionListener listener) {
+ mListener = listener;
+ }
+
+ public void viewContact(Uri contactUri) {
+ setSelectedContactUri(contactUri, false, false, true, false);
+ if (mListener != null) mListener.onViewContactAction(contactUri);
+ }
+
+ public void deleteContact(Uri contactUri) {
+ if (mListener != null) mListener.onDeleteContactAction(contactUri);
+ }
+
+ private void notifyInvalidSelection() {
+ if (mListener != null) mListener.onInvalidSelection();
+ }
+
+ @Override
+ protected void finish() {
+ super.finish();
+ if (mListener != null) mListener.onFinishAction();
+ }
+
+ private void saveSelectedUri(Uri contactUri) {
+ if (isSearchMode()) {
+ return;
+ }
+
+ ContactListFilter.storeToPreferences(mPrefs, mFilter);
+
+ SharedPreferences.Editor editor = mPrefs.edit();
+ if (contactUri == null) {
+ editor.remove(getPersistentSelectionKey());
+ } else {
+ editor.putString(getPersistentSelectionKey(), contactUri.toString());
+ }
+ editor.apply();
+ }
+
+ private void restoreSelectedUri(boolean willReloadData) {
+ String selectedUri = mPrefs.getString(getPersistentSelectionKey(), null);
+ if (selectedUri == null) {
+ setSelectedContactUri(null, false, false, false, willReloadData);
+ } else {
+ setSelectedContactUri(Uri.parse(selectedUri), false, false, false, willReloadData);
+ }
+ }
+
+ private void saveFilter() {
+ ContactListFilter.storeToPreferences(mPrefs, mFilter);
+ }
+
+ private void restoreFilter() {
+ if (mPrefs != null) {
+ mFilter = ContactListFilter.restoreDefaultPreferences(mPrefs);
+ }
+ }
+
+ private String getPersistentSelectionKey() {
+ if (mFilter == null) {
+ return mPersistentSelectionPrefix;
+ } else {
+ return mPersistentSelectionPrefix + "-" + mFilter.getId();
+ }
+ }
+
+ public boolean isOptionsMenuChanged() {
+ // This fragment does not have an option menu of its own
+ return false;
+ }
+
+ @Override
+ protected void onItemClick(int position, long id) {
+ final Uri uri = getAdapter().getContactUri(position);
+ if (uri == null) {
+ return;
+ }
+ viewContact(uri);
+ }
+
+ @Override
+ protected ContactListAdapter createListAdapter() {
+ DefaultContactListAdapter adapter = new DefaultContactListAdapter(getContext());
+ adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
+ adapter.setDisplayPhotos(true);
+ adapter.setPhotoPosition(
+ ContactListItemView.getDefaultPhotoPosition(/* opposite = */ false));
+
+ return adapter;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (mInCallPluginInfo != null) {
+ try {
+ if (view == mLoginBtn) {
+ if (mInCallPluginInfo.mCallMethodInfo.mLoginIntent != null) {
+ mInCallPluginInfo.mCallMethodInfo.mLoginIntent.send();
+ }
+ } else if (view == mEmptyView) {
+ if (mInCallPluginInfo.mCallMethodInfo.mDefaultDirectorySearchIntent != null) {
+ mInCallPluginInfo.mCallMethodInfo.mDefaultDirectorySearchIntent.send();
+ }
+ }
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "PendingIntent exception", e);
+ }
+ }
+ }
+
+ public synchronized void updateInCallPluginInfo(InCallPluginInfo pluginInfo) {
+ mInCallPluginInfo = pluginInfo;
+ updatePluginView();
+ }
+
+ public synchronized void updatePluginView() {
+ if (mInCallPluginInfo != null) {
+ if (DEBUG) Log.d(TAG, "updatePluginView: " + mInCallPluginInfo.mCallMethodInfo.mName);
+ if (mListView != null && mLoginView != null) {
+ if (mInCallPluginInfo.mCallMethodInfo.mIsAuthenticated) {
+ // Show list view
+ mLoginView.setVisibility(View.GONE);
+ mListView.setVisibility(View.VISIBLE);
+ if (mEmptyView != null) {
+ mEmptyView.setVisibility(View.VISIBLE);
+ ((ListView) mListView).setEmptyView(mEmptyView);
+ initEmptyView();
+ }
+ } else {
+ // Show login view
+ ((ListView)mListView).setEmptyView(null);
+ mListView.setVisibility(View.GONE);
+ if (mEmptyView != null) {
+ mEmptyView.setVisibility(View.GONE);
+ }
+ mLoginView.setVisibility(View.VISIBLE);
+ if (mLoginIconView != null) {
+ if (mInCallPluginInfo.mCallMethodInfo.mLoginIconId == 0) {
+ // plugin does not provide a valid icon
+ mLoginIconView.setVisibility(View.GONE);
+ } else {
+ // plugin provides a valid icon, this method is called from the main
+ // thread so needs to start an AsyncTask to look up the Drawable from
+ // resource Id
+ if (mInCallPluginInfo.mCallMethodInfo.mLoginIcon != null) {
+ // TODO: may need to self load in the case of restore before plugin
+ // update
+ mLoginIconView.setImageDrawable(
+ mInCallPluginInfo.mCallMethodInfo.mLoginIcon);
+ } else {
+ if (mInCallPluginInfo.mCallMethodInfo.mLoginIconId != 0) {
+ new GetDrawableAsyncTask().execute();
+ }
+ }
+ }
+ }
+ if (mLoginMsg != null) {
+ if (!TextUtils.isEmpty(mInCallPluginInfo.mCallMethodInfo.mLoginSubtitle)) {
+ mLoginMsg.setText(mInCallPluginInfo.mCallMethodInfo.mLoginSubtitle);
+ } else {
+ mLoginMsg.setText(getResources().getString(R.string.plugin_login_msg,
+ mInCallPluginInfo.mCallMethodInfo.mName));
+ }
+ }
+ }
+ }
+ mPrefsFileName = PREFS_FILE_PREFIX +
+ mInCallPluginInfo.mCallMethodInfo.mComponent.getClassName();
+ if (mPrefs != null) {
+ setFilter(ContactListFilter.createAccountFilter(
+ mInCallPluginInfo.mCallMethodInfo.mAccountType,
+ mInCallPluginInfo.mCallMethodInfo.mAccountHandle,
+ null,
+ null));
+ }
+ }
+ }
+
+ private class GetDrawableAsyncTask extends AsyncTask<Void, Void, Drawable> {
+ @Override
+ protected Drawable doInBackground(Void... params) {
+ Drawable loginIcon = null;
+ if (mInCallPluginInfo.mCallMethodInfo.mLoginIconId != 0) {
+ loginIcon = InCallPluginUtils.getDrawable
+ (PluginContactBrowseListFragment.this.getActivity(),
+ mInCallPluginInfo.mCallMethodInfo.mLoginIconId,
+ mInCallPluginInfo.mCallMethodInfo.mComponent);
+ }
+ return loginIcon;
+ }
+
+ @Override
+ protected void onPostExecute(Drawable icon) {
+ if (icon != null) {
+ mLoginIconView.setImageDrawable(icon);
+ }
+ }
+ }
+
+ private void initEmptyView() {
+ if (mEmptyView != null) {
+ Resources rc = getResources();
+ mEmptyView.setText(rc.getString(R.string.plugin_empty_list_text,
+ TextUtils.isEmpty(mInCallPluginInfo.mCallMethodInfo.mName) ? "" :
+ mInCallPluginInfo.mCallMethodInfo.mName),
+ TextView.BufferType.SPANNABLE);
+ Spannable actionText = new SpannableString(
+ rc.getString(R.string.plugin_empty_list_action_text));
+ actionText.setSpan(new ForegroundColorSpan(rc.getColor(R.color.primary_color)), 0,
+ actionText.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+ actionText.setSpan(new StyleSpan(Typeface.BOLD), 0, actionText.length(), Spannable
+ .SPAN_INCLUSIVE_EXCLUSIVE);
+ mEmptyView.append(actionText);
+ mEmptyView.setOnClickListener(this);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
index ba4021b6c..47a62f96a 100644
--- a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
+++ b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
@@ -29,7 +29,10 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.widget.CardView;
import android.text.Spannable;
+import android.text.SpannableString;
import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+
import android.transition.ChangeBounds;
import android.transition.Fade;
import android.transition.Transition;
@@ -54,6 +57,8 @@ import android.widget.TextView;
import com.android.contacts.R;
import com.android.contacts.common.dialog.CallSubjectDialog;
+import com.android.phone.common.incall.CallMethodInfo;
+
import java.util.ArrayList;
import java.util.List;
@@ -97,27 +102,34 @@ public class ExpandingEntryCardView extends CardView {
// Button action will open the call with subject dialog.
public static final int ACTION_CALL_WITH_SUBJECT = 3;
- private final int mId;
- private final Drawable mIcon;
- private final String mHeader;
- private final String mSubHeader;
- private final Drawable mSubHeaderIcon;
- private final String mText;
- private final Drawable mTextIcon;
+ private int mId;
+ private Drawable mIcon;
+ private String mHeader;
+ private String mSubHeader;
+ private Drawable mSubHeaderIcon;
+ private String mActionText;
+ private String mText;
+ private Drawable mTextIcon;
private Spannable mPrimaryContentDescription;
- private final Intent mIntent;
- private final Drawable mAlternateIcon;
- private final Intent mAlternateIntent;
- private final String mAlternateContentDescription;
- private final boolean mShouldApplyColor;
- private final boolean mIsEditable;
- private final EntryContextMenuInfo mEntryContextMenuInfo;
- private final Drawable mThirdIcon;
- private final Intent mThirdIntent;
- private final String mThirdContentDescription;
- private final int mIconResourceId;
- private final int mThirdAction;
- private final Bundle mThirdExtras;
+ private Intent mIntent;
+ private Drawable mAlternateIcon;
+ private Intent mAlternateIntent;
+ private String mAlternateContentDescription;
+ private boolean mShouldApplyColor;
+ private boolean mIsEditable;
+ private EntryContextMenuInfo mEntryContextMenuInfo;
+ private String mThirdText;
+ private Drawable mThirdIcon;
+ private Intent mThirdIntent;
+ private String mThirdContentDescription;
+ private int mThirdAction;
+ private Bundle mThirdExtras;
+ private int mIconResourceId;
+ private CallMethodInfo mCallMethodInfo;
+ private List<Entry> mContainerList;
+ private List<List<Entry>> mParentList;
+
+ public Entry () {}
public Entry(int id, Drawable mainIcon, String header, String subHeader,
Drawable subHeaderIcon, String text, Drawable textIcon,
@@ -132,6 +144,7 @@ public class ExpandingEntryCardView extends CardView {
mHeader = header;
mSubHeader = subHeader;
mSubHeaderIcon = subHeaderIcon;
+ mActionText = null;
mText = text;
mTextIcon = textIcon;
mPrimaryContentDescription = primaryContentDescription;
@@ -142,6 +155,7 @@ public class ExpandingEntryCardView extends CardView {
mShouldApplyColor = shouldApplyColor;
mIsEditable = isEditable;
mEntryContextMenuInfo = entryContextMenuInfo;
+ mThirdText = null;
mThirdIcon = thirdIcon;
mThirdIntent = thirdIntent;
mThirdContentDescription = thirdContentDescription;
@@ -150,6 +164,39 @@ public class ExpandingEntryCardView extends CardView {
mIconResourceId = iconResourceId;
}
+ public Entry(int id, Drawable mainIcon, String header, String subHeader, Drawable
+ subHeaderIcon, String actionText, Intent intent, Drawable alternateIcon, Intent
+ alternateIntent, String thirdText, Drawable thirdIcon, Intent thirdIntent, int
+ iconResourceId, CallMethodInfo cmi, List<Entry> containerList,
+ List<List<Entry>> parentList) {
+ mId = id;
+ mIcon = mainIcon;
+ mHeader = header;
+ mSubHeader = subHeader;
+ mSubHeaderIcon = subHeaderIcon;
+ mActionText = actionText;
+ mText = null;
+ mTextIcon = null;
+ mPrimaryContentDescription = null;
+ mIntent = intent;
+ mAlternateIcon = alternateIcon;
+ mAlternateIntent = alternateIntent;
+ mAlternateContentDescription = null;
+ mShouldApplyColor = false;
+ mIsEditable = false;
+ mEntryContextMenuInfo = null;
+ mThirdText = thirdText;
+ mThirdIcon = thirdIcon;
+ mThirdIntent = thirdIntent;
+ mThirdContentDescription = null;
+ mThirdAction = -1;
+ mThirdExtras = null;
+ mIconResourceId = iconResourceId;
+ mCallMethodInfo = cmi;
+ mContainerList = containerList;
+ mParentList = parentList;
+ }
+
Drawable getIcon() {
return mIcon;
}
@@ -166,6 +213,10 @@ public class ExpandingEntryCardView extends CardView {
return mSubHeaderIcon;
}
+ public String getActionText() {
+ return mActionText;
+ }
+
public String getText() {
return mText;
}
@@ -210,6 +261,10 @@ public class ExpandingEntryCardView extends CardView {
return mEntryContextMenuInfo;
}
+ String getThirdText() {
+ return mThirdText;
+ }
+
Drawable getThirdIcon() {
return mThirdIcon;
}
@@ -233,6 +288,18 @@ public class ExpandingEntryCardView extends CardView {
public Bundle getThirdExtras() {
return mThirdExtras;
}
+
+ CallMethodInfo getCallMethodInfo() {
+ return mCallMethodInfo;
+ }
+
+ List<List<Entry>> getParentList() {
+ return mParentList;
+ }
+
+ List<Entry> getContainerList() {
+ return mContainerList;
+ }
}
public interface ExpandingEntryCardViewListener {
@@ -667,12 +734,17 @@ public class ExpandingEntryCardView extends CardView {
Drawable alternateIcon = entry.getAlternateIcon();
if (alternateIcon != null) {
alternateIcon.mutate();
- alternateIcon.setColorFilter(mThemeColorFilter);
+ if (entry.getCallMethodInfo() == null) {
+ // not from a plugin
+ alternateIcon.setColorFilter(mThemeColorFilter);
+ }
}
Drawable thirdIcon = entry.getThirdIcon();
if (thirdIcon != null) {
thirdIcon.mutate();
- thirdIcon.setColorFilter(mThemeColorFilter);
+ if (entry.getCallMethodInfo() == null) {
+ thirdIcon.setColorFilter(mThemeColorFilter);
+ }
}
}
}
@@ -708,7 +780,11 @@ public class ExpandingEntryCardView extends CardView {
final TextView subHeader = (TextView) view.findViewById(R.id.sub_header);
if (!TextUtils.isEmpty(entry.getSubHeader())) {
- subHeader.setText(entry.getSubHeader());
+ if (entry.getActionText() == null) {
+ subHeader.setText(entry.getSubHeader());
+ } else {
+ subHeader.setText(entry.getSubHeader(), TextView.BufferType.SPANNABLE);
+ }
} else {
subHeader.setVisibility(View.GONE);
}
@@ -720,6 +796,13 @@ public class ExpandingEntryCardView extends CardView {
subHeaderIcon.setVisibility(View.GONE);
}
+ if (!TextUtils.isEmpty(entry.getActionText())) {
+ Spannable actionText = new SpannableString(" " + entry.getActionText());
+ actionText.setSpan(new ForegroundColorSpan(mThemeColor), 0, actionText.length(),
+ Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+ subHeader.append(actionText);
+ }
+
final TextView text = (TextView) view.findViewById(R.id.text);
if (!TextUtils.isEmpty(entry.getText())) {
text.setText(entry.getText());
@@ -736,7 +819,7 @@ public class ExpandingEntryCardView extends CardView {
if (entry.getIntent() != null) {
view.setOnClickListener(mOnClickListener);
- view.setTag(new EntryTag(entry.getId(), entry.getIntent()));
+ view.setTag(new EntryTag(entry.getId(), entry.getIntent(), entry));
}
if (entry.getIntent() == null && entry.getEntryContextMenuInfo() == null) {
@@ -774,11 +857,12 @@ public class ExpandingEntryCardView extends CardView {
final ImageView alternateIcon = (ImageView) view.findViewById(R.id.icon_alternate);
final ImageView thirdIcon = (ImageView) view.findViewById(R.id.third_icon);
+ final TextView thirdTextView = (TextView) view.findViewById(R.id.third_text);
if (entry.getAlternateIcon() != null && entry.getAlternateIntent() != null) {
alternateIcon.setImageDrawable(entry.getAlternateIcon());
alternateIcon.setOnClickListener(mOnClickListener);
- alternateIcon.setTag(new EntryTag(entry.getId(), entry.getAlternateIntent()));
+ alternateIcon.setTag(new EntryTag(entry.getId(), entry.getAlternateIntent(), entry));
alternateIcon.setVisibility(View.VISIBLE);
alternateIcon.setContentDescription(entry.getAlternateContentDescription());
}
@@ -787,7 +871,7 @@ public class ExpandingEntryCardView extends CardView {
thirdIcon.setImageDrawable(entry.getThirdIcon());
if (entry.getThirdAction() == Entry.ACTION_INTENT) {
thirdIcon.setOnClickListener(mOnClickListener);
- thirdIcon.setTag(new EntryTag(entry.getId(), entry.getThirdIntent()));
+ thirdIcon.setTag(new EntryTag(entry.getId(), entry.getThirdIntent(), entry));
} else if (entry.getThirdAction() == Entry.ACTION_CALL_WITH_SUBJECT) {
thirdIcon.setOnClickListener(new View.OnClickListener() {
@Override
@@ -809,6 +893,16 @@ public class ExpandingEntryCardView extends CardView {
thirdIcon.setContentDescription(entry.getThirdContentDescription());
}
+ if (!TextUtils.isEmpty(entry.getThirdText()) && entry.getThirdIntent() != null) {
+ thirdTextView.setText(entry.getThirdText());
+ thirdTextView.setOnClickListener(mOnClickListener);
+ thirdTextView.setTag(new EntryTag(entry.getId(), entry.getThirdIntent(), entry));
+ thirdTextView.setTextColor(mThemeColor);
+ thirdTextView.setVisibility(View.VISIBLE);
+ } else {
+ thirdTextView.setVisibility(View.GONE);
+ }
+
// Set a custom touch listener for expanding the extra icon touch areas
view.setOnTouchListener(new EntryTouchListener(view, alternateIcon, thirdIcon));
view.setOnCreateContextMenuListener(mOnCreateContextMenuListener);
@@ -842,8 +936,10 @@ public class ExpandingEntryCardView extends CardView {
&& mEntries.get(0).size() > 1) {
numberOfMimeTypesShown--;
}
- // Inflate badges if not yet created
+ // Inflate badges if not yet created or not up to date
if (mBadges.size() < mEntries.size() - numberOfMimeTypesShown) {
+ mBadges.clear();
+ mBadgeIds.clear();
for (int i = numberOfMimeTypesShown; i < mEntries.size(); i++) {
Drawable badgeDrawable = mEntries.get(i).get(0).getIcon();
int badgeResourceId = mEntries.get(i).get(0).getIconResourceId();
@@ -1105,10 +1201,18 @@ public class ExpandingEntryCardView extends CardView {
static final class EntryTag {
private final int mId;
private final Intent mIntent;
+ private final Entry mEntry;
public EntryTag(int id, Intent intent) {
mId = id;
mIntent = intent;
+ mEntry = null;
+ }
+
+ public EntryTag(int id, Intent intent, Entry entry) {
+ mId = id;
+ mIntent = intent;
+ mEntry = entry;
}
public int getId() {
@@ -1118,6 +1222,10 @@ public class ExpandingEntryCardView extends CardView {
public Intent getIntent() {
return mIntent;
}
+
+ public Entry getEntry() {
+ return mEntry;
+ }
}
/**
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 65c4d3cf7..d2ce66ee5 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -23,14 +23,16 @@ import android.app.Activity;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.LoaderManager.LoaderCallbacks;
+import android.app.PendingIntent;
import android.app.SearchManager;
import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
import android.content.ContentValues;
import android.content.ContentUris;
-import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
@@ -47,9 +49,11 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Trace;
+import android.preference.PreferenceManager;
import android.provider.CalendarContract;
import android.os.Handler;
import android.os.Message;
+import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
@@ -61,7 +65,7 @@ import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Relation;
import android.provider.ContactsContract.CommonDataKinds.SipAddress;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
import android.provider.ContactsContract.Contacts;
@@ -139,8 +143,6 @@ import com.android.contacts.common.model.dataitem.StructuredPostalDataItem;
import com.android.contacts.common.model.dataitem.WebsiteDataItem;
import com.android.contacts.common.util.BlockContactHelper;
import com.android.contacts.common.util.ImplicitIntentsUtil;
-import com.android.contacts.common.MoreContactUtils;
-import com.android.contacts.common.SimContactsConstants;
import com.android.contacts.common.util.BitmapUtil;
import com.android.contacts.common.util.DateUtils;
import com.android.contacts.common.util.MaterialColorMapUtils;
@@ -150,7 +152,10 @@ import com.android.contacts.common.util.ViewUtil;
import com.android.contacts.detail.ContactDisplayUtils;
import com.android.contacts.editor.ContactEditorFragment;
import com.android.contacts.editor.EditorIntents;
+import com.android.contacts.incall.InCallPluginHelper;
+import com.android.contacts.incall.InCallPluginUtils;
import com.android.contacts.interactions.CalendarInteractionsLoader;
+import com.android.contacts.interactions.CallLogInteraction;
import com.android.contacts.interactions.CallLogInteractionsLoader;
import com.android.contacts.interactions.ContactDeletionInteraction;
import com.android.contacts.interactions.ContactInteraction;
@@ -168,6 +173,10 @@ import com.android.contacts.util.StructuredPostalUtils;
import com.android.contacts.widget.MultiShrinkScroller;
import com.android.contacts.widget.MultiShrinkScroller.MultiShrinkScrollerListener;
import com.android.contacts.widget.QuickContactImageView;
+import com.android.phone.common.incall.CallMethodHelper;
+import com.android.phone.common.incall.CallMethodInfo;
+import com.cyanogen.ambient.incall.extension.OriginCodes;
+import com.cyanogen.ambient.plugin.PluginStatus;
import com.android.contactsbind.HelpUtils;
import com.cyanogen.lookup.phonenumber.provider.LookupProviderImpl;
@@ -183,9 +192,12 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Mostly translucent {@link Activity} that shows QuickContact dialog. It loads
@@ -203,6 +215,7 @@ public class QuickContactActivity extends ContactsActivity implements
public static final int MODE_FULLY_EXPANDED = 4;
private static final String TAG = "QuickContact";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String KEY_THEME_COLOR = "theme_color";
@@ -215,6 +228,16 @@ public class QuickContactActivity extends ContactsActivity implements
/** This is the Intent action to install a shortcut in the launcher. */
private static final String ACTION_INSTALL_SHORTCUT =
"com.android.launcher.action.INSTALL_SHORTCUT";
+ public static final String ACTION_INCALL_PLUGIN_LOGIN =
+ "com.android.contacts.quickcontact.INCALL_PLUGIN_LOGIN";
+ public static final String ACTION_INCALL_PLUGIN_INVITE =
+ "com.android.contacts.quickcontact.INCALL_PLUGIN_INVITE";
+ public static final String ACTION_INCALL_PLUGIN_DIRECTORY_SEARCH =
+ "com.android.contacts.quickcontact.INCALL_PLUGIN_DIRECTORY_SEARCH";
+ public static final String ACTION_INCALL_PLUGIN_INSTALL =
+ "com.android.contacts.quickcontact.ACTION_INCALL_PLUGIN_INSTALL";
+ public static final String ACTION_INCALL_PLUGIN_DISMISS_NUDGE =
+ "com.android.contacts.quickcontact.ACTION_INCALL_PLUGIN_DISMISS_NUDGE";
@SuppressWarnings("deprecation")
private static final String LEGACY_AUTHORITY = android.provider.Contacts.AUTHORITY;
@@ -250,8 +273,11 @@ public class QuickContactActivity extends ContactsActivity implements
private ExpandingEntryCardView mAboutCard;
private MultiShrinkScroller mScroller;
private SelectAccountDialogFragmentListener mSelectAccountFragmentListener;
- private AsyncTask<Void, Void, Cp2DataCardModel> mEntriesAndActionsTask;
+ private AsyncTask<Contact, Void, Cp2DataCardModel> mEntriesAndActionsTask;
private AsyncTask<Void, Void, Void> mRecentDataTask;
+ private AtomicBoolean mIsUpdating;
+ private boolean mNeedPluginUpdate = false;
+ private static final String CALL_METHOD_SUBSCRIBER_ID = TAG;
/**
* The last copy of Cp2DataCardModel that was passed to {@link #populateContactAndAboutCard}.
*/
@@ -283,7 +309,7 @@ public class QuickContactActivity extends ContactsActivity implements
private Target mContactBitmapTarget;
private BlockContactHelper mBlockContactHelper;
-
+ private Object mLock = new Object();
/**
* {@link #LEADING_MIMETYPES} is used to sort MIME-types.
*
@@ -307,11 +333,23 @@ public class QuickContactActivity extends ContactsActivity implements
Identity.CONTENT_ITEM_TYPE,
Note.CONTENT_ITEM_TYPE);
+ // Common mime types that are loaded and present to users before plugins load to avoid delays
+ private static final List<String> COMMON_MIMETYPES = Lists.newArrayList(
+ Email.CONTENT_ITEM_TYPE,
+ Nickname.CONTENT_ITEM_TYPE,
+ Phone.CONTENT_ITEM_TYPE,
+ SipAddress.CONTENT_ITEM_TYPE,
+ StructuredName.CONTENT_ITEM_TYPE,
+ StructuredPostal.CONTENT_ITEM_TYPE);
+
private static final BidiFormatter sBidiFormatter = BidiFormatter.getInstance();
/** Id for the background contact loader */
private static final int LOADER_CONTACT_ID = 0;
+ private static final String KEY_LOADER_EXTRA_PLUGIN_INFO =
+ QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_PLUGIN_INFO";
+
private static final String KEY_LOADER_EXTRA_PHONES =
QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_PHONES";
@@ -336,7 +374,8 @@ public class QuickContactActivity extends ContactsActivity implements
private static final int MIN_NUM_CONTACT_ENTRIES_SHOWN = 3;
private static final int MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN = 3;
private static final int CARD_ENTRY_ID_EDIT_CONTACT = -2;
-
+ private static final int CARD_ENTRY_ID_INCALL_PLUGIN = -3;
+ public static final int CARD_ENTRY_ID_INCALL_PLUGIN_CALL = -4;
private static final int MAX_NUM_LENGTH = 3; // add limit length to show IP call item
private static final int[] mRecentLoaderIds = new int[]{
@@ -363,13 +402,28 @@ public class QuickContactActivity extends ContactsActivity implements
}
final EntryTag entryTag = (EntryTag) entryTagObject;
final Intent intent = entryTag.getIntent();
- final int dataId = entryTag.getId();
+ int dataId = entryTag.getId();
if (dataId == CARD_ENTRY_ID_EDIT_CONTACT) {
editContact();
return;
}
+ // InCall plugin invite or nudges entries
+ if (dataId == CARD_ENTRY_ID_INCALL_PLUGIN) {
+ handleInCallPluginAction(entryTag);
+ return;
+ }
+
+ boolean isPlugin = false;
+ // InCall plugin callable entries, need to retrieve the data id
+ if (dataId == CARD_ENTRY_ID_INCALL_PLUGIN_CALL) {
+ dataId = (int) intent.getLongExtra(InCallPluginUtils.KEY_DATA_ID, -1);
+ if (intent.getAction() == null) {
+ isPlugin = true;
+ }
+ }
+
// Default to USAGE_TYPE_CALL. Usage is summed among all types for sorting each data id
// so the exact usage type is not necessary in all cases
String usageType = DataUsageFeedback.USAGE_TYPE_CALL;
@@ -414,7 +468,29 @@ public class QuickContactActivity extends ContactsActivity implements
mHasIntentLaunched = true;
try {
- ImplicitIntentsUtil.startActivityInAppIfPossible(QuickContactActivity.this, intent);
+ if (isPlugin) {
+ // it's an entry from plugin, route to this call
+ CallMethodInfo cmi = null;
+ if (entryTag.getEntry() == null ||
+ entryTag.getEntry().getCallMethodInfo() == null) {
+ cmi = InCallPluginHelper.getCallMethod(ComponentName.unflattenFromString(
+ intent.getStringExtra(InCallPluginUtils.KEY_COMPONENT)));
+ cmi.placeCall(OriginCodes.CONTACTS_CARD,
+ intent.getStringExtra(InCallPluginUtils.KEY_NUMBER),
+ getBaseContext(), false, false,
+ intent.getStringExtra(InCallPluginUtils.KEY_MIMETYPE));
+ } else {
+ cmi = entryTag.getEntry().getCallMethodInfo();
+ cmi.placeCall(OriginCodes.CONTACTS_CARD,
+ intent.getStringExtra(InCallPluginUtils.KEY_NUMBER),
+ getBaseContext(), false,
+ TextUtils.isEmpty(intent.getStringExtra(InCallPluginUtils
+ .KEY_MIMETYPE)), null);
+ }
+ } else {
+ ImplicitIntentsUtil
+ .startActivityInAppIfPossible(QuickContactActivity.this, intent);
+ }
} catch (SecurityException ex) {
Toast.makeText(QuickContactActivity.this, R.string.missing_app,
Toast.LENGTH_SHORT).show();
@@ -872,6 +948,7 @@ public class QuickContactActivity extends ContactsActivity implements
}
});
}
+ mIsUpdating = new AtomicBoolean(false);
processIntent(getIntent());
mBlockContactHelper = new BlockContactHelper(this, new LookupProviderImpl(this));
if (mContactData != null) {
@@ -930,7 +1007,18 @@ public class QuickContactActivity extends ContactsActivity implements
mLookupUri = lookupUri;
mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
+ Contact contact = null;
+ if (mLookupUri == null) {
+ // See if a URI has been attached as an extra
+ mLookupUri = intent.getParcelableExtra(Contact.CONTACT_URI_EXTRA);
+ if (mLookupUri != null) {
+ contact = ContactLoader.parseEncodedContactEntity(mLookupUri,
+ ContactLoader.EncodedContactEntitySchemaVersion.ENHANCED_CALLER_META_DATA);
+ }
+ }
+
if (mLookupUri == null) {
+ if (DEBUG) Log.d(TAG, "mLookupUri null!");
finish();
return;
}
@@ -943,7 +1031,8 @@ public class QuickContactActivity extends ContactsActivity implements
}
if (contact != null) {
- bindContactData(contact);
+ // looked up encoded contact
+ checkAndBindContactData(contact, false, false);
} else if (oldLookupUri == null) {
mContactLoader = (ContactLoader) getLoaderManager().initLoader(
LOADER_CONTACT_ID, null, mLoaderContactCallbacks);
@@ -1076,36 +1165,58 @@ public class QuickContactActivity extends ContactsActivity implements
Trace.endSection();
- mEntriesAndActionsTask = new AsyncTask<Void, Void, Cp2DataCardModel>() {
+ mEntriesAndActionsTask = new EntriesAndActionTask().execute(data);
+ }
- @Override
- protected Cp2DataCardModel doInBackground(
- Void... params) {
- return generateDataModelFromContact(data);
+ private class EntriesAndActionTask extends AsyncTask<Contact, Void, Cp2DataCardModel> {
+ private Contact mData;
+
+ @Override
+ protected Cp2DataCardModel doInBackground(Contact... params) {
+ // gather plugin information
+ if (DEBUG) Log.d(TAG, "+++doInBackground");
+ if (isCancelled()) {
+ // onCancelled will be called upon return
+ if (DEBUG) Log.d(TAG, "+++doInBackground cancelled");
+ return null;
}
+ mData = params[0];
+ Cp2DataCardModel model = generateDataModelFromContact(mData);
+ if (DEBUG) Log.d(TAG, "---doInBackground");
+ return model;
+ }
- @Override
- protected void onPostExecute(Cp2DataCardModel cardDataModel) {
- super.onPostExecute(cardDataModel);
- // Check that original AsyncTask parameters are still valid and the activity
- // is still running before binding to UI. A new intent could invalidate
- // the results, for example.
- if (data == mContactData && !isCancelled()) {
- bindDataToCards(cardDataModel);
- showActivity();
- }
+ @Override
+ protected void onPostExecute(Cp2DataCardModel cardDataModel) {
+ super.onPostExecute(cardDataModel);
+ // Check that original AsyncTask parameters are still valid and the activity
+ // is still running before binding to UI. A new intent could invalidate
+ // the results, for example.
+ if (DEBUG) Log.d(TAG, "+++onPostExecute");
+ if (mData == mContactData && !isCancelled() && cardDataModel != null) {
+ bindDataToCards(cardDataModel);
+ showActivity();
+ mIsUpdating.set(false);
}
- };
- mEntriesAndActionsTask.execute();
+ if (mNeedPluginUpdate) {
+ mNeedPluginUpdate = false;
+ updatePlugins(null);
+ }
+ if (DEBUG) Log.d(TAG, "---onPostExecute");
+ }
}
private void bindDataToCards(Cp2DataCardModel cp2DataCardModel) {
+ if (DEBUG) Log.d(TAG, "+++bindDataToCards");
startInteractionLoaders(cp2DataCardModel);
populateContactAndAboutCard(cp2DataCardModel);
+ if (DEBUG) Log.d(TAG, "---bindDataToCards");
}
private void startInteractionLoaders(Cp2DataCardModel cp2DataCardModel) {
final Map<String, List<DataItem>> dataItemsMap = cp2DataCardModel.dataItemsMap;
+ final Map<ComponentName, List<String>> pluginAccountsMap = cp2DataCardModel
+ .pluginAccountsMap;
final List<DataItem> phoneDataItems = dataItemsMap.get(Phone.CONTENT_ITEM_TYPE);
if (phoneDataItems != null && phoneDataItems.size() == 1) {
mOnlyOnePhoneNumber = true;
@@ -1119,7 +1230,7 @@ public class QuickContactActivity extends ContactsActivity implements
}
final Bundle phonesExtraBundle = new Bundle();
phonesExtraBundle.putStringArray(KEY_LOADER_EXTRA_PHONES, phoneNumbers);
-
+ phonesExtraBundle.putSerializable(KEY_LOADER_EXTRA_PLUGIN_INFO, (HashMap)pluginAccountsMap);
Trace.beginSection("start sms loader");
getLoaderManager().initLoader(
LOADER_SMS_ID,
@@ -1190,6 +1301,12 @@ public class QuickContactActivity extends ContactsActivity implements
@Override
protected void onResume() {
super.onResume();
+ if (InCallPluginHelper.subscribe(CALL_METHOD_SUBSCRIBER_ID, pluginsUpdatedReceiver)) {
+ if (DEBUG) Log.d(TAG, "InCallPluginHelper infoReady");
+ InCallPluginHelper.refreshDynamicItems();
+ } else {
+ if (DEBUG) Log.d(TAG, "InCallPluginHelper info NOT Ready");
+ }
// If returning from a launched activity, repopulate the contact and about card
if (mHasIntentLaunched) {
mHasIntentLaunched = false;
@@ -1205,7 +1322,14 @@ public class QuickContactActivity extends ContactsActivity implements
}
}
- private void populateContactAndAboutCard(Cp2DataCardModel cp2DataCardModel) {
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ InCallPluginHelper.unsubscribe(CALL_METHOD_SUBSCRIBER_ID);
+ }
+
+ private synchronized void populateContactAndAboutCard(Cp2DataCardModel cp2DataCardModel) {
mCachedCp2DataCardModel = cp2DataCardModel;
if (mHasIntentLaunched || cp2DataCardModel == null) {
return;
@@ -1283,7 +1407,7 @@ public class QuickContactActivity extends ContactsActivity implements
mExpandingEntryCardViewListener,
mScroller);
- if (contactCardEntries.size() == 0 && aboutCardEntries.size() == 0) {
+ if ((contactCardEntries.size() == 0 && aboutCardEntries.size() == 0)) {
initializeNoContactDetailCard();
} else {
mNoContactDetailsCard.setVisibility(View.GONE);
@@ -1379,13 +1503,31 @@ public class QuickContactActivity extends ContactsActivity implements
* amongst mimetype. The map goes from mimetype string to the sorted list of data items within
* mimetype
*/
- private Cp2DataCardModel generateDataModelFromContact(
- Contact data) {
+ private Cp2DataCardModel generateDataModelFromContact(Contact data) {
Trace.beginSection("Build data items map");
final Map<String, List<DataItem>> dataItemsMap = new HashMap<>();
final ResolveCache cache = ResolveCache.getInstance(this);
+ Set<String> pluginMimeExcluded;
+ Set<String> pluginMimeIncluded;
+ if (InCallPluginHelper.infoReady()) {
+ mCallMethodMap = (HashMap<ComponentName, CallMethodInfo>)
+ InCallPluginHelper.getAllEnabledAndHiddenCallMethods();
+ pluginMimeExcluded = InCallPluginHelper.getAllEnabledVideoImMimeSet();
+ pluginMimeIncluded = InCallPluginHelper.getAllEnabledVoiceMimeSet();
+ if (DEBUG) {
+ Log.d(TAG, "plugins size:" + mCallMethodMap.size());
+ Log.d(TAG, "mimeExcluded size:" + pluginMimeExcluded.size());
+ Log.d(TAG, "mimeIncluded size:" + pluginMimeIncluded.size());
+ }
+ } else {
+ mCallMethodMap = new HashMap<ComponentName, CallMethodInfo>();
+ pluginMimeExcluded = new HashSet<String>();
+ pluginMimeIncluded = new HashSet<String>();
+ }
+
+ HashMap<String, RawContact> pluginRawContactsMap = new HashMap<String, RawContact>();
for (RawContact rawContact : data.getRawContacts()) {
for (DataItem dataItem : rawContact.getDataItems()) {
dataItem.setRawContactId(rawContact.getId());
@@ -1402,6 +1544,11 @@ public class QuickContactActivity extends ContactsActivity implements
final boolean hasData = !TextUtils.isEmpty(dataItem.buildDataString(this,
dataKind));
+ // the mime type has been consolidated in the plugin entry, skip
+ if (pluginMimeExcluded.contains(mimeType)) continue;
+ if (pluginMimeIncluded.contains(mimeType)) {
+ pluginRawContactsMap.put(mimeType, rawContact);
+ }
if (isMimeExcluded(mimeType) || !hasData) continue;
@@ -1443,13 +1590,25 @@ public class QuickContactActivity extends ContactsActivity implements
final List<List<Entry>> contactCardEntries = new ArrayList<>();
final List<List<Entry>> aboutCardEntries = buildAboutCardEntries(dataItemsMap);
final MutableString aboutCardName = new MutableString();
-
+ HashMap<ComponentName, List<String>> pluginAccountsMap = new HashMap<ComponentName,
+ List<String>>();
for (int i = 0; i < dataItemsList.size(); ++i) {
final List<DataItem> dataItemsByMimeType = dataItemsList.get(i);
final DataItem topDataItem = dataItemsByMimeType.get(0);
if (SORTED_ABOUT_CARD_MIMETYPES.contains(topDataItem.getMimeType())) {
// About card mimetypes are built in buildAboutCardEntries, skip here
continue;
+ } else if (pluginRawContactsMap.containsKey(topDataItem.getMimeType())) {
+ List<Entry> pluginEntries = incallPluginDataItemsToEntries(dataItemsByMimeType,
+ data, pluginRawContactsMap.get(topDataItem.getMimeType()),
+ contactCardEntries, pluginAccountsMap);
+ if (pluginEntries.size() > 0) {
+ if (DEBUG) {
+ Log.d(TAG, "pluginEntries added to contactCardEntries:" +
+ pluginEntries.size());
+ }
+ contactCardEntries.add(pluginEntries);
+ }
} else {
List<Entry> contactEntries = dataItemsToEntries(dataItemsList.get(i),
aboutCardName);
@@ -1458,6 +1617,10 @@ public class QuickContactActivity extends ContactsActivity implements
}
}
}
+ if (!mContactData.isUserProfile() && InCallPluginHelper.infoReady() && mCallMethodMap
+ .size() > 0) {
+ addAllInCallPluginOtherEntries(contactCardEntries, pluginRawContactsMap);
+ }
Trace.endSection();
@@ -1466,6 +1629,7 @@ public class QuickContactActivity extends ContactsActivity implements
dataModel.aboutCardEntries = aboutCardEntries;
dataModel.contactCardEntries = contactCardEntries;
dataModel.dataItemsMap = dataItemsMap;
+ dataModel.pluginAccountsMap = pluginAccountsMap;
return dataModel;
}
@@ -1479,9 +1643,11 @@ public class QuickContactActivity extends ContactsActivity implements
* are in sorted order using mWithinMimeTypeDataItemComparator.
*/
public Map<String, List<DataItem>> dataItemsMap;
+ public Map<String, List<DataItem>> hideDataItemsMap;
public List<List<Entry>> aboutCardEntries;
public List<List<Entry>> contactCardEntries;
public String customAboutCardName;
+ public Map<ComponentName, List<String>> pluginAccountsMap;
}
private static class MutableString {
@@ -2220,7 +2386,18 @@ public class QuickContactActivity extends ContactsActivity implements
if (interaction == null) {
continue;
}
- entries.add(new Entry(/* id = */ -1,
+ boolean applyColor = true;
+ int dataId = -1;
+ // only CallLogInteraction classes can be from plugin (not SmsInteraction
+ // and CalendarInteraction)
+ if (CallLogInteraction.class.isInstance(interaction) && !TextUtils.isEmpty(
+ ((CallLogInteraction)interaction).getPluginPkgName())) {
+ applyColor = false;
+ dataId = CARD_ENTRY_ID_INCALL_PLUGIN_CALL;
+ }
+
+ // need to associate number as well
+ entries.add(new Entry(dataId,
interaction.getIcon(this),
interaction.getViewHeader(this),
interaction.getViewBody(this),
@@ -2232,7 +2409,7 @@ public class QuickContactActivity extends ContactsActivity implements
/* alternateIcon = */ null,
/* alternateIntent = */ null,
/* alternateContentDescription = */ null,
- /* shouldApplyColor = */ true,
+ applyColor,
/* isEditable = */ false,
/* EntryContextMenuInfo = */ null,
/* thirdIcon = */ null,
@@ -2276,10 +2453,7 @@ public class QuickContactActivity extends ContactsActivity implements
finish();
return;
}
- mBlockContactHelper.setContactInfo(data);
- mBlockContactHelper.gatherDataInBackground();
- bindContactData(data);
-
+ checkAndBindContactData(data, true, false);
} finally {
Trace.endSection();
}
@@ -2298,6 +2472,33 @@ public class QuickContactActivity extends ContactsActivity implements
}
};
+ private synchronized void checkAndBindContactData(Contact contact, boolean withBlockHelper,
+ boolean onlyStartAsyncTask) {
+ if (DEBUG) Log.d(TAG, "checkAndBindContactData," + withBlockHelper + " " +
+ onlyStartAsyncTask);
+ // Update pending Intents
+ InCallPluginHelper.refreshPendingIntents(InCallPluginUtils.getInCallContactInfo(contact));
+
+ if (mIsUpdating.get() && mEntriesAndActionsTask != null && !mEntriesAndActionsTask
+ .isCancelled()) {
+ mEntriesAndActionsTask.cancel(true);
+ }
+ mIsUpdating.set(true);
+ if (withBlockHelper) {
+ destroyInteractionLoaders();
+ mBlockContactHelper.setContactInfo(contact);
+ mBlockContactHelper.gatherDataInBackground();
+ bindContactData(contact);
+ } else {
+ destroyInteractionLoaders();
+ if (onlyStartAsyncTask) {
+ mEntriesAndActionsTask = new EntriesAndActionTask().execute(contact);
+ } else {
+ bindContactData(contact);
+ }
+ }
+ }
+
@Override
public void onBackPressed() {
if (mScroller != null) {
@@ -2351,6 +2552,7 @@ public class QuickContactActivity extends ContactsActivity implements
loader = new CallLogInteractionsLoader(
QuickContactActivity.this,
args.getStringArray(KEY_LOADER_EXTRA_PHONES),
+ (HashMap) args.getSerializable(KEY_LOADER_EXTRA_PLUGIN_INFO),
MAX_CALL_LOG_RETRIEVE);
}
return loader;
@@ -2461,6 +2663,7 @@ public class QuickContactActivity extends ContactsActivity implements
protected void onStop() {
super.onStop();
+ mIsUpdating.set(false);
if (mEntriesAndActionsTask != null) {
// Once the activity is stopped, we will no longer want to bind mEntriesAndActionsTask's
// results on the UI thread. In some circumstances Activities are killed without
@@ -3245,4 +3448,286 @@ public class QuickContactActivity extends ContactsActivity implements
return true;
return false;
}
+
+ private void addAllInCallPluginOtherEntries(List<List<Entry>> parentList,
+ HashMap<String, RawContact> pluginRawContactMap) {
+ for (ComponentName cn : mCallMethodMap.keySet()) {
+ CallMethodInfo cmi = mCallMethodMap.get(cn);
+ addInCallPluginEntries(cmi, parentList, pluginRawContactMap.containsKey(cmi.mMimeType));
+ }
+ }
+
+ private void addInCallPluginEntries(CallMethodInfo cmi, List<List<Entry>> parentList,
+ boolean hasPluginAccount) {
+ final Resources res = getResources();
+ List<Entry> containerList;
+ Entry entry;
+ if (cmi.mStatus == PluginStatus.HIDDEN) {
+ if (cmi.mLoginNudgeEnable) {
+ containerList = new ArrayList<Entry>();
+ // install nudge
+ entry = new Entry(CARD_ENTRY_ID_INCALL_PLUGIN,
+ cmi.mBrandIcon,
+ null,
+ cmi.mInstallNudgeSubtitle,
+ null,
+ cmi.mInstallNudgeActionText,
+ new Intent(ACTION_INCALL_PLUGIN_INSTALL),
+ res.getDrawable(R.drawable.ic_close),
+ new Intent(ACTION_INCALL_PLUGIN_DISMISS_NUDGE),
+ null,
+ null,
+ null,
+ cmi.mBrandIconId,
+ cmi,
+ containerList, parentList);
+ if (DEBUG) Log.d(TAG, "Adding INSTALL NUDGE");
+ containerList.add(entry);
+ parentList.add(containerList);
+ }
+ } else if (cmi.mStatus == PluginStatus.ENABLED) {
+ if (!hasPluginAccount) {
+ if (cmi.mIsAuthenticated) {
+ // Invite
+ containerList = new ArrayList<Entry>();
+ entry = new Entry(CARD_ENTRY_ID_INCALL_PLUGIN,
+ cmi.mBrandIcon,
+ res.getString(R.string.incall_plugin_directory_search, cmi.mName),
+ null,
+ null,
+ null,
+ new Intent(ACTION_INCALL_PLUGIN_DIRECTORY_SEARCH),
+ null,
+ null,
+ res.getString(R.string.incall_plugin_invite),
+ null,
+ new Intent(ACTION_INCALL_PLUGIN_INVITE),
+ cmi.mBrandIconId,
+ cmi,
+ containerList, parentList);
+ if (DEBUG) Log.d(TAG, "Adding INVITE ENTRY");
+ containerList.add(entry);
+ parentList.add(containerList);
+ } else {
+ // login nudge
+ if (cmi.mLoginNudgeEnable) {
+ containerList = new ArrayList<Entry>();
+ entry = new Entry(CARD_ENTRY_ID_INCALL_PLUGIN,
+ cmi.mBrandIcon,
+ null,
+ cmi.mInstallNudgeSubtitle,
+ null,
+ cmi.mInstallNudgeActionText,
+ new Intent(ACTION_INCALL_PLUGIN_LOGIN),
+ res.getDrawable(R.drawable.ic_close),
+ new Intent(ACTION_INCALL_PLUGIN_DISMISS_NUDGE),
+ null,
+ null,
+ null,
+ cmi.mBrandIconId,
+ cmi,
+ containerList, parentList);
+ if (DEBUG) Log.d(TAG, "Adding LOGIN NUDGE");
+ containerList.add(entry);
+ parentList.add(containerList);
+ }
+ }
+ }
+ }
+ }
+
+ // Create new Entries
+ private List<Entry> incallPluginDataItemsToEntries(List<DataItem> dataItems, Contact contact,
+ RawContact rawContact, List<List<Entry>> parentList, HashMap<ComponentName,
+ List<String>> pluginAccountMap) {
+ List<Entry> entries = new ArrayList<Entry>();
+ for (DataItem dataItem : dataItems) {
+ CallMethodInfo cmi =
+ InCallPluginHelper.getMethodForMimeType(dataItem.getMimeType(), true);
+ Entry entry;
+ String contactAccountHandle = rawContact.getSourceId();
+ if (cmi.mIsAuthenticated) {
+ // user signed in, consolidate entries
+ InCallPluginUtils.PresenceInfo presenceInfo = InCallPluginUtils.lookupPresenceInfo(
+ this, contact, rawContact);
+ Intent callIntent = InCallPluginUtils.getVoiceMimeIntent(cmi.mMimeType, dataItem,
+ cmi, contactAccountHandle);
+ entry = new Entry(CARD_ENTRY_ID_INCALL_PLUGIN_CALL,
+ cmi.mBrandIcon,
+ contactAccountHandle,
+ presenceInfo.mStatusMsg,
+ presenceInfo.mPresenceIcon,
+ null,
+ callIntent,
+ cmi.mImIcon,
+ InCallPluginUtils.getImMimeIntent(cmi.mImMimeType, rawContact),
+ null,
+ cmi.mVoiceIcon,
+ callIntent,
+ cmi.mBrandIconId,
+ cmi,
+ entries, parentList);
+ if (DEBUG) Log.d(TAG, "Adding CALL ENTRY");
+ } else {
+ // user signed out, list the contact's account name, and action set to
+ // launch sign in
+ final Resources res = getResources();
+ entry = new Entry(CARD_ENTRY_ID_INCALL_PLUGIN,
+ cmi.mBrandIcon,
+ contactAccountHandle,
+ res.getString(R.string.incall_plugin_account_subheader, cmi.mName),
+ null, null,
+ new Intent(ACTION_INCALL_PLUGIN_LOGIN),
+ null, null, null, null,
+ null, cmi.mBrandIconId,
+ cmi, entries, parentList);
+ if (DEBUG) Log.d(TAG, "Adding ACCOUNT ENTRY");
+ }
+ entries.add(entry);
+ // gather account list for interactions
+ List<String> accountsList;
+ if (!pluginAccountMap.containsKey(cmi.mComponent)) {
+ accountsList = new ArrayList<String>();
+ pluginAccountMap.put(cmi.mComponent, accountsList);
+ } else {
+ accountsList = pluginAccountMap.get(cmi.mComponent);
+ }
+ accountsList.add(contactAccountHandle);
+
+ }
+ return entries;
+ }
+
+ private void handleInCallPluginAction(EntryTag tag) {
+ if (tag == null) {
+ return;
+ }
+ Entry entry = tag.getEntry();
+ if (entry == null) {
+ return;
+ }
+ CallMethodInfo cmiStored = entry.getCallMethodInfo();
+ CallMethodInfo cmi = InCallPluginHelper.getCallMethod(cmiStored.mComponent);
+ Intent intent = tag.getIntent();
+ if (cmi == null || intent == null) {
+ return;
+ }
+ try {
+ if(intent.getAction().equals(ACTION_INCALL_PLUGIN_INSTALL)) {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" +
+ cmi.mDependentPackage)));
+ } else if (intent.getAction().equals(ACTION_INCALL_PLUGIN_DISMISS_NUDGE)) {
+ String nudgeKey = intent.getStringExtra(InCallPluginUtils
+ .KEY_NUDGE_KEY);
+ dismissNudge(tag, nudgeKey);
+ } else if (intent.getAction().equals(ACTION_INCALL_PLUGIN_LOGIN)) {
+ if (cmi.mLoginIntent != null) {
+ cmi.mLoginIntent.send();
+ }
+ } else if (intent.getAction().equals(ACTION_INCALL_PLUGIN_INVITE)) {
+ if (cmi.mInviteIntent != null) {
+ cmi.mInviteIntent.send();
+ } else {
+ cmi.mInviteIntent = InCallPluginHelper.getInviteIntentSync(cmi.mComponent,
+ InCallPluginUtils.getInCallContactInfo(mContactData));
+ if (cmi.mInviteIntent != null) {
+ cmi.mInviteIntent.send();
+ }
+ }
+ } else if (intent.getAction().equals(ACTION_INCALL_PLUGIN_DIRECTORY_SEARCH)) {
+ if (cmi.mDirectorySearchIntent != null) {
+ cmi.mDirectorySearchIntent.send();
+ } else {
+ cmi.mDirectorySearchIntent =
+ InCallPluginHelper.getDirectorySearchIntentSync(cmi.mComponent,
+ InCallPluginUtils.getInCallContactInfo(mContactData).mLookupUri);
+ if (cmi.mDirectorySearchIntent != null) {
+ cmi.mDirectorySearchIntent.send();
+ }
+ }
+ }
+ } catch (PendingIntent.CanceledException e) {
+ if (DEBUG) Log.d(TAG, "handleInCallPluginAction ", e);
+ }
+ }
+
+ private void dismissNudge(EntryTag tag, String nudgeKey) {
+ // Write the dimissal to preferences
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = sp.edit();
+ if (tag == null) {
+ return;
+ }
+ Entry entry = tag.getEntry();
+ CallMethodInfo cmi = entry.getCallMethodInfo();
+ final List<List<Entry>> cardEntries = entry.getParentList();
+ List<Entry> containerList = entry.getContainerList();
+
+ // disable the nudge type
+ editor.putBoolean(cmi.mComponent.getClassName() + "." + nudgeKey, false);
+ editor.apply();
+ // Find the entry and remove it
+ cardEntries.remove(containerList);
+
+ // Repopulate the view
+ mContactCard.initialize(cardEntries,
+ MIN_NUM_CONTACT_ENTRIES_SHOWN,
+ mContactCard.isExpanded(),
+ false,
+ mExpandingEntryCardViewListener,
+ mScroller);
+ mContactCard.setVisibility(View.VISIBLE);
+ }
+
+ private CallMethodHelper.CallMethodReceiver pluginsUpdatedReceiver =
+ new CallMethodHelper.CallMethodReceiver() {
+ @Override
+ public void onChanged(HashMap<ComponentName, CallMethodInfo> callMethodInfos) {
+ updatePlugins(callMethodInfos);
+ }
+ };
+
+ // Global CallMethod map that keeps track of the currently displayed plugins
+ HashMap<ComponentName, CallMethodInfo> mCallMethodMap = new HashMap<ComponentName, CallMethodInfo>();
+
+ private void updatePlugins(HashMap<ComponentName, CallMethodInfo> callMethods) {
+ if (DEBUG) Log.d(TAG, "+++updatePlugins");
+ HashMap<ComponentName, CallMethodInfo> newCmMap = (HashMap<ComponentName, CallMethodInfo>)
+ InCallPluginHelper.getAllEnabledAndHiddenCallMethods();
+ boolean updateNeeded = false;
+ if (mContactData == null) {
+ return;
+ }
+ if (mIsUpdating.get() || mCachedCp2DataCardModel == null) {
+ if (DEBUG) Log.d(TAG, "---updatePlugins null return");
+ checkAndBindContactData(mContactData, false, true);
+ return;
+ }
+ // Check if update or removal is needed
+ for (ComponentName cn : mCallMethodMap.keySet()) {
+ CallMethodInfo cmi = mCallMethodMap.get(cn);
+ if (newCmMap.containsKey(cn)) {
+ // Check if update needed
+ CallMethodInfo newCmi = newCmMap.remove(cn);
+ if (!newCmi.equals(cmi) || newCmi.mIsAuthenticated != cmi.mIsAuthenticated) {
+ updateNeeded = true;
+ }
+ } else {
+ // need to remove plugins
+ if (DEBUG) Log.d(TAG, "updatePlugins removed");
+ updateNeeded = true;
+ }
+ }
+ // Check if need to add new plugins
+ if (newCmMap.size() > 0) {
+ if (DEBUG) Log.d(TAG, "updatePlugins new plugins");
+ updateNeeded = true;
+ }
+
+ if (updateNeeded) {
+ if (DEBUG) Log.d(TAG, "updatePlugins updateNeeded");
+ checkAndBindContactData(mContactData, false, true);
+ }
+ if (DEBUG) Log.d(TAG, "---updatePlugins return");
+ }
}