summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Bird <sbird@cyngn.com>2016-01-21 19:04:13 -0800
committerRichard MacGregor <rmacgregor@cyngn.com>2016-04-08 10:42:49 -0700
commita6049e69f130b9f5557465315112c5ca6647b3b9 (patch)
tree11d9eb75ea2a396cc33a489bc6a3755b31bdd5e3
parentc5f5b115f589f95d02fb0fd3690d098ca6397974 (diff)
downloadandroid_packages_apps_PhoneCommon-a6049e69f130b9f5557465315112c5ca6647b3b9.tar.gz
android_packages_apps_PhoneCommon-a6049e69f130b9f5557465315112c5ca6647b3b9.tar.bz2
android_packages_apps_PhoneCommon-a6049e69f130b9f5557465315112c5ca6647b3b9.zip
Bring CallMethod items here
Change-Id: Iec207cac5b513199e7a1707e4e6617f607d78771
-rw-r--r--Android.mk1
-rw-r--r--res/values/cm_strings.xml3
-rw-r--r--src-ambient/ambient/AmbientConnection.java66
-rw-r--r--src-ambient/ambient/SingletonHolder.java50
-rw-r--r--src-ambient/incall/CallMethodHelper.java694
-rw-r--r--src-ambient/incall/CallMethodInfo.java214
-rw-r--r--src-ambient/incall/CallMethodSpinnerAdapter.java (renamed from src/com/android/phone/common/incall/CallMethodSpinnerAdapter.java)1
-rw-r--r--src-ambient/incall/CallMethodUtils.java108
8 files changed, 1136 insertions, 1 deletions
diff --git a/Android.mk b/Android.mk
index c12b37d..79cdd91 100644
--- a/Android.mk
+++ b/Android.mk
@@ -18,6 +18,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, src-ambient)
LOCAL_RESOURCE_FILES := $(addprefix $(LOCAL_PATH)/, res)
LOCAL_PACKAGE_NAME := com.android.phone.common
diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml
index c246c8b..2979616 100644
--- a/res/values/cm_strings.xml
+++ b/res/values/cm_strings.xml
@@ -24,4 +24,7 @@
<!-- Call method spinner - Default No SIM -->
<string name="call_method_spinner_item_no_sim">No SIM card</string>
<string name="call_method_spinner_item_unknown_sim">SIM <xliff:g id="slot_number">%d</xliff:g></string>
+
+ <string name="invalid_number_text">"%1$s could not make this call. Is the number correct?"</string>
+
</resources>
diff --git a/src-ambient/ambient/AmbientConnection.java b/src-ambient/ambient/AmbientConnection.java
new file mode 100644
index 0000000..ed4c0c0
--- /dev/null
+++ b/src-ambient/ambient/AmbientConnection.java
@@ -0,0 +1,66 @@
+package com.android.phone.common.ambient;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.cyanogen.ambient.analytics.AnalyticsServices;
+import com.cyanogen.ambient.callerinfo.CallerInfoServices;
+import com.cyanogen.ambient.common.ConnectionResult;
+import com.cyanogen.ambient.common.api.AmbientApiClient;
+import com.cyanogen.ambient.discovery.DiscoveryManagerServices;
+import com.cyanogen.ambient.discovery.NudgeServices;
+import com.cyanogen.ambient.incall.InCallServices;
+
+/**
+ * Holds Ambient Client for all PIM apps
+ */
+public class AmbientConnection {
+
+ public static final SingletonHolder<AmbientApiClient, Context> CLIENT =
+ new SingletonHolder<AmbientApiClient, Context>() {
+ private static final String TAG = "Dialer.AmbientSingletonHolder";
+
+ @Override
+ protected AmbientApiClient create(Context context) {
+ AmbientApiClient client = new AmbientApiClient.Builder(context)
+ .addApi(AnalyticsServices.API)
+ .addApi(InCallServices.API)
+ .addApi(CallerInfoServices.API)
+ .addApi(NudgeServices.API)
+ .addApi(DiscoveryManagerServices.API)
+ .build();
+
+ client.registerConnectionFailedListener(
+ new AmbientApiClient.OnConnectionFailedListener() {
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ Log.w(TAG, "Ambient connection failed: " + result);
+ }
+ });
+ client.registerDisconnectionListener(
+ new AmbientApiClient.OnDisconnectionListener() {
+ @Override
+ public void onDisconnection() {
+ Log.d(TAG, "Ambient connection disconnected");
+ }
+ });
+ client.registerConnectionCallbacks(
+ new AmbientApiClient.ConnectionCallbacks() {
+ @Override
+ public void onConnected(Bundle connectionHint) {
+ Log.d(TAG, "Ambient connection established");
+ }
+
+ @Override
+ public void onConnectionSuspended(int cause) {
+ Log.d(TAG, "Ambient connection suspended");
+ }
+ });
+ client.connect();
+ return client;
+ }
+ };
+
+
+}
diff --git a/src-ambient/ambient/SingletonHolder.java b/src-ambient/ambient/SingletonHolder.java
new file mode 100644
index 0000000..52890fc
--- /dev/null
+++ b/src-ambient/ambient/SingletonHolder.java
@@ -0,0 +1,50 @@
+package com.android.phone.common.ambient;
+
+/**
+ * Encapsulates a threadsafe singleton pattern.
+ *
+ * This class is designed to be used as a public constant, living within a class that has a private constructor.
+ * It defines a {@link #create(I)} method that will only ever be called once, upon the first call of {@link #get(I)}.
+ * That method is responsible for creating the actual singleton instance, and that instance will be returned for all
+ * future calls of {@link #get(I)}.
+ *
+ * Example:
+ * <code>
+ * public class FooSingleton {
+ * public static final SingletonHolder&lt;FooSingleton, ParamObject&gt; HOLDER =
+ * new SingletonHolder&lt;FooSingleton, ParamObject&gt;() {
+ * @Override
+ * protected FooSingleton create(ParamObject param) {
+ * return new FooSingleton(param);
+ * }
+ * };
+ *
+ * private FooSingleton(ParamObject param) {
+ *
+ * }
+ * }
+ *
+ * // somewhere else
+ * FooSingleton.HOLDER.get(params).doStuff();
+ * </code>
+ * @param <E> The type of the class to hold as a singleton.
+ * @param <I> A parameter object to use during creation of the singleton object.
+ */
+public abstract class SingletonHolder<E, I> {
+ private E mInstance;
+ private final Object LOCK = new Object();
+
+ public final E get(I initializer) {
+ if (null == mInstance) {
+ synchronized (LOCK) {
+ if (null == mInstance) {
+ mInstance = create(initializer);
+ }
+ }
+ }
+
+ return mInstance;
+ }
+
+ protected abstract E create(I initializer);
+}
diff --git a/src-ambient/incall/CallMethodHelper.java b/src-ambient/incall/CallMethodHelper.java
new file mode 100644
index 0000000..5c38193
--- /dev/null
+++ b/src-ambient/incall/CallMethodHelper.java
@@ -0,0 +1,694 @@
+/*
+ * Copyright (C) 2015 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.phone.common.incall;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.widget.Toast;
+import com.android.phone.common.ambient.AmbientConnection;
+import com.android.phone.common.util.StartInCallCallReceiver;
+import com.android.phone.common.R;
+import com.cyanogen.ambient.analytics.Event;
+import com.cyanogen.ambient.common.api.AmbientApiClient;
+import com.cyanogen.ambient.common.api.Result;
+import com.cyanogen.ambient.common.api.ResultCallback;
+import com.cyanogen.ambient.discovery.NudgeServices;
+import com.cyanogen.ambient.discovery.results.BundleResult;
+import com.cyanogen.ambient.discovery.util.NudgeKey;
+import com.cyanogen.ambient.incall.InCallApi;
+import com.cyanogen.ambient.incall.InCallPluginStatus;
+import com.cyanogen.ambient.incall.InCallServices;
+import com.cyanogen.ambient.incall.extension.CreditBalance;
+import com.cyanogen.ambient.incall.extension.CreditInfo;
+import com.cyanogen.ambient.incall.extension.GetCreditInfoResult;
+import com.cyanogen.ambient.incall.extension.ICallCreditListener;
+import com.cyanogen.ambient.incall.extension.StatusCodes;
+import com.cyanogen.ambient.incall.results.AuthenticationStateResult;
+import com.cyanogen.ambient.incall.results.GetCreditInfoResultResult;
+import com.cyanogen.ambient.incall.results.InCallProviderInfoResult;
+import com.cyanogen.ambient.incall.results.InstalledPluginsResult;
+import com.cyanogen.ambient.incall.results.MimeTypeResult;
+import com.cyanogen.ambient.incall.results.PendingIntentResult;
+import com.cyanogen.ambient.incall.results.PluginStatusResult;
+import com.cyanogen.ambient.incall.util.InCallProviderInfo;
+import com.google.common.base.Joiner;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.cyanogen.ambient.incall.util.InCallHelper.NO_COLOR;
+
+/**
+ * Call Method Helper - In charge of keeping a running and updated hashmap of all InCallProviders
+ * currently installed.
+ *
+ * Fragments and Activities can subscribe to changes with subscribe.
+ *
+ */
+public class CallMethodHelper {
+
+ private static CallMethodHelper sInstance;
+
+ AmbientApiClient mClient;
+ Context mContext;
+ InCallApi mInCallApi;
+ Handler mMainHandler;
+ private static List<ComponentName> mInstalledPlugins;
+ private static HashMap<ComponentName, CallMethodInfo> mCallMethodInfos = new HashMap<>();
+ private static HashMap<ComponentName, ICallCreditListener> mCallCreditListeners = new
+ HashMap<>();
+ private static HashMap<String, CallMethodReceiver> mRegisteredClients = new HashMap<>();
+ private static boolean dataHasBeenBroadcastPreviously = false;
+
+ // To prevent multiple broadcasts and force us to wait for all items to be complete
+ // this is the count of callbacks we should get for each item. Increase this if we add more.
+ private static int EXPECTED_RESULT_CALLBACKS = 8;
+
+ // To prevent multiple broadcasts and force us to wait for all items to be complete
+ // this is the count of callbacks we should get for each item. Increase this if we add more.
+ private static int EXPECTED_DYNAMIC_RESULT_CALLBACKS = 2;
+
+ // Keeps track of the number of callbacks we have from AmbientCore. Reset this to 0
+ // immediately after all callbacks are accounted for.
+ private static int callbackCount = 0;
+
+ private static final String TAG = CallMethodHelper.class.getSimpleName();
+ private static final boolean DEBUG = true;
+
+ public interface CallMethodReceiver {
+ void onChanged(HashMap<ComponentName, CallMethodInfo> callMethodInfos);
+ }
+
+ /**
+ * Broadcasts mCallMethodInfos to all registered clients on the Main thread.
+ */
+ private static void broadcast() {
+ getInstance().mMainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ for (CallMethodReceiver client : mRegisteredClients.values()) {
+ client.onChanged(mCallMethodInfos);
+ }
+ if (DEBUG) {
+ for (CallMethodInfo cmi : mCallMethodInfos.values()) {
+ Log.v("BIRD", "Broadcast: " + cmi.mName);
+ }
+ }
+ dataHasBeenBroadcastPreviously = true;
+ callbackCount = 0;
+ }
+ });
+ }
+
+ /**
+ * Helper method for subscribed clients to remove any item that is not enabled from the hashmap
+ * @param input HashMap returned from a broadcast
+ * @param output HashMap with only enabled items
+ */
+ public static void removeDisabled(HashMap<ComponentName, CallMethodInfo> input,
+ HashMap<ComponentName, CallMethodInfo> output) {
+
+ for (Map.Entry<ComponentName, CallMethodInfo> entry : input.entrySet()) {
+ ComponentName key = entry.getKey();
+ CallMethodInfo value = entry.getValue();
+
+ if (value.mStatus == InCallPluginStatus.ENABLED) {
+ output.put(key, value);
+ }
+ }
+ }
+
+ /***
+ * Registers the client, on register returns boolean if
+ * callMethodInfo data is already collected and the initial broadcast has been sent.
+ * @param id unique string for the client
+ * @param cmr client receiver
+ * @return boolean isempty
+ */
+ public static synchronized boolean subscribe(String id, CallMethodReceiver cmr) {
+ mRegisteredClients.put(id, cmr);
+
+ for (ComponentName callCreditProvider : mCallCreditListeners.keySet()) {
+ if (mCallCreditListeners.get(callCreditProvider) == null) {
+ /* CallCreditListenerImpl listener = CallCreditListenerImpl.getInstance
+ (callCreditProvider);
+ getInstance().mInCallApi.addCreditListener(getInstance().mClient,
+ callCreditProvider, listener);
+ mCallCreditListeners.put(callCreditProvider, listener); */
+ }
+ }
+
+ return dataHasBeenBroadcastPreviously;
+ }
+
+ /**
+ * Unsubscribes the client. All clients should unsubscribe when they are removed.
+ * @param id of the client to remove
+ */
+ public static synchronized void unsubscribe(String id) {
+ mRegisteredClients.remove(id);
+
+ if (mRegisteredClients.size() == 0) {
+ for (ComponentName callCreditProvider : mCallCreditListeners.keySet()) {
+ if (mCallCreditListeners.get(callCreditProvider) != null) {
+ getInstance().mInCallApi.removeCreditListener(getInstance().mClient,
+ callCreditProvider, mCallCreditListeners.get(callCreditProvider));
+ }
+ }
+ }
+ }
+
+ /**
+ * Get a single instance of our call method helper. There should only be ever one instance.
+ * @return
+ */
+ private static synchronized CallMethodHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new CallMethodHelper();
+ }
+ return sInstance;
+ }
+
+ /**
+ * Generic CallResultReceiver with basic error handling
+ * @param cmi
+ * @return
+ */
+ public static StartInCallCallReceiver getVoIPResultReceiver(final CallMethodInfo cmi,
+ final String originCode) {
+ StartInCallCallReceiver svcrr =
+ new StartInCallCallReceiver(new Handler(Looper.getMainLooper()));
+
+ svcrr.setReceiver(new StartInCallCallReceiver.Receiver() {
+
+ @Override
+ public void onReceiveResult(int resultCode, Bundle resultData) {
+ if (DEBUG) Log.i(TAG, "Got Start VoIP Call result callback code = " + resultCode);
+
+ switch (resultCode) {
+ case StatusCodes.StartCall.CALL_FAILURE_INSUFFICIENT_CREDITS:
+ case StatusCodes.StartCall.CALL_FAILURE_INVALID_NUMBER:
+ case StatusCodes.StartCall.CALL_FAILURE_TIMEOUT:
+ case StatusCodes.StartCall.CALL_FAILURE_UNAUTHENTICATED:
+ case StatusCodes.StartCall.CALL_FAILURE:
+
+ String text = getInstance().mContext.getResources()
+ .getString(R.string.invalid_number_text);
+ text = String.format(text, cmi.mName);
+ Toast.makeText(getInstance().mContext, text, Toast.LENGTH_LONG).show();
+ break;
+ case StatusCodes.StartCall.CALL_CONNECTED:
+ break;
+ case StatusCodes.StartCall.HANDOVER_CONNECTED:
+ break;
+ default:
+ Log.i(TAG, "Nothing to do for this Start VoIP Call resultcode = "
+ + resultCode);
+ break;
+ }
+
+ }
+
+ });
+
+ return svcrr;
+ }
+
+ /**
+ * Start our Helper and kick off our first ModCore queries.
+ * @param context
+ */
+ public static void init(Context context) {
+ CallMethodHelper helper = getInstance();
+ helper.mContext = context;
+ helper.mClient = AmbientConnection.CLIENT.get(context);
+ helper.mInCallApi = InCallServices.getInstance();
+ helper.mMainHandler = new Handler(context.getMainLooper());
+ refresh();
+ }
+
+ /**
+ * *sip* ahhhh so refreshing
+ */
+ public static void refresh() {
+ updateCallPlugins();
+ }
+
+ /**
+ * Refresh just the possibly changing items
+ *
+ * This should only be called once dataHasBeenBroadcastPreviously is true.
+ */
+ public static void refreshDynamic() {
+ for(ComponentName cn : mCallMethodInfos.keySet()) {
+ getCreditInfo(cn, true);
+ getCallMethodAuthenticated(cn, true);
+ }
+ }
+
+ /**
+ * This is helpful for items that don't want to subscribe to updates or for things that
+ * need a quick CMI and have a component name.
+ * @param cn Component name wanted.
+ * @return specific call method when given a component name.
+ */
+ public static CallMethodInfo getCallMethod(ComponentName cn) {
+ if (mCallMethodInfos.containsKey(cn)) {
+ return mCallMethodInfos.get(cn);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * This is useful for items that subscribe after the initial broadcast has been sent out and
+ * need to go get some data right away
+ * @return the current HashMap of CMIs.
+ */
+ public static HashMap<ComponentName, CallMethodInfo> getAllCallMethods() {
+ return mCallMethodInfos;
+ }
+
+ /**
+ * A few items need a list of mime types in a comma delimited list. Since we are already
+ * querying all the plugins. We can easily build this list ahead of time.
+ *
+ * Items that require this should subscribe and grab this updated list when needed.
+ * @return string of all (not limited to enabled) mime types
+ */
+ public static String getAllMimeTypes() {
+ String mimeTypes = "";
+
+ List<String> mimeTypesList = new ArrayList<>();
+
+ for (CallMethodInfo cmi : mCallMethodInfos.values()) {
+ mimeTypesList.add(cmi.mMimeType);
+ }
+
+ if (!mimeTypesList.isEmpty()) {
+ mimeTypes = Joiner.on(",").skipNulls().join(mimeTypesList);
+ }
+ return mimeTypes;
+ }
+
+ /**
+ * A few items need a list of mime types in a comma delimited list. Since we are already
+ * querying all the plugins. We can easily build this list ahead of time.
+ *
+ * Items that require this should subscribe and grab this updated list when needed.
+ * @return string of enabled mime types
+ */
+ public static String getAllEnabledMimeTypes() {
+ String mimeTypes = "";
+
+ List<String> enabledMimeTypes = new ArrayList<>();
+
+ for (CallMethodInfo cmi : mCallMethodInfos.values()) {
+ if (cmi.mStatus == InCallPluginStatus.ENABLED) {
+ enabledMimeTypes.add(cmi.mMimeType);
+ }
+ }
+
+ if (!enabledMimeTypes.isEmpty()) {
+ mimeTypes = Joiner.on(",").skipNulls().join(enabledMimeTypes);
+ }
+ return mimeTypes;
+ }
+
+ public static void updateCreditInfo(ComponentName name, GetCreditInfoResult gcir) {
+ CallMethodInfo cmi = getCallMethodIfExists(name);
+ if (cmi != null) {
+ if (gcir == null || gcir.creditInfo == null) {
+ // Build zero credit dummy if no result found.
+ cmi.mProviderCreditInfo =
+ new CreditInfo(new CreditBalance(0, null), null);
+ } else {
+ cmi.mProviderCreditInfo = gcir.creditInfo;
+ }
+
+ // Since a CallMethodInfo object was updated here, we should let the subscribers know
+ broadcast();
+ }
+ }
+
+ /**
+ * Broadcast to subscribers once we know we've gathered all our data. Do not do this until we
+ * have everything we need for sure.
+ *
+ * This method is called after every callback from AmbientCore. We will keep track of all of
+ * the callbacks, once we have accounted for all callbacks from all plugins, we can go ahead
+ * and update subscribers.
+ */
+ private static void maybeBroadcastToSubscribers() {
+ maybeBroadcastToSubscribers(false);
+ }
+
+ private static void maybeBroadcastToSubscribers(boolean broadcastDynamic) {
+ int expectedCount;
+
+ ++callbackCount;
+ if (broadcastDynamic) {
+ expectedCount = EXPECTED_DYNAMIC_RESULT_CALLBACKS;
+ } else {
+ expectedCount = EXPECTED_RESULT_CALLBACKS;
+ }
+
+ if (callbackCount == (expectedCount * mInstalledPlugins.size())) {
+ // we are on the last item. broadcast updated hashmap
+ broadcast();
+ }
+ }
+
+ /**
+ * In order to speed up the process we make calls for providers that may be invalid
+ * To prevent this, make sure every resultcallback uses this before filling in the hashmap.
+ * @param cn componentname
+ * @return callmethodinfo if valid, otherwise null
+ */
+ public static CallMethodInfo getCallMethodIfExists(ComponentName cn) {
+ if (mCallMethodInfos.containsKey(cn)) {
+ return mCallMethodInfos.get(cn);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Prepare to query and fire off ModCore calls in all directions
+ */
+ private static void updateCallPlugins() {
+ getInstance().mInCallApi.getInstalledPlugins(getInstance().mClient)
+ .setResultCallback(new ResultCallback<InstalledPluginsResult>() {
+ @Override
+ public void onResult(InstalledPluginsResult installedPluginsResult) {
+ // got installed components
+ mInstalledPlugins = installedPluginsResult.components;
+
+ mCallMethodInfos.clear();
+
+ if (mInstalledPlugins.size() == 0) {
+ broadcast();
+ }
+
+ for (ComponentName cn : mInstalledPlugins) {
+ mCallMethodInfos.put(cn, new CallMethodInfo());
+ getCallMethodInfo(cn);
+ getCallMethodStatus(cn);
+ getCallMethodMimeType(cn);
+ getCallMethodAuthenticated(cn, false);
+ getSettingsIntent(cn);
+ getCreditInfo(cn, false);
+ getManageCreditsIntent(cn);
+ checkLowCreditConfig(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.
+ }
+ }
+ });
+ }
+
+ /**
+ * Get our basic CMI metadata
+ * @param cn
+ */
+ private static void getCallMethodInfo(final ComponentName cn) {
+ getInstance().mInCallApi.getProviderInfo(getInstance().mClient, cn)
+ .setResultCallback(new ResultCallback<InCallProviderInfoResult>() {
+ @Override
+ public void onResult(InCallProviderInfoResult inCallProviderInfoResult) {
+
+ InCallProviderInfo icpi = inCallProviderInfoResult.inCallProviderInfo;
+ if (icpi == null) {
+ mCallMethodInfos.remove(cn);
+ return;
+ }
+
+ PackageManager packageManager = getInstance().mContext.getPackageManager();
+
+ Resources pluginResources = null;
+ try {
+ pluginResources = packageManager.getResourcesForApplication(
+ cn.getPackageName());
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Plugin isn't installed: " + cn);
+ mCallMethodInfos.remove(cn);
+ return;
+ }
+
+ synchronized (mCallMethodInfos) {
+ CallMethodInfo cmi = getCallMethodIfExists(cn);
+
+ if (cmi == null) {
+ return;
+ }
+
+ try {
+ cmi.mBrandIcon = pluginResources.getDrawable(icpi.getBrandIcon(), null);
+ cmi.mBadgeIcon = pluginResources.getDrawable(icpi.getBadgeIcon(), null);
+ cmi.mLoginIcon = pluginResources.getDrawable(icpi.getLoginIcon(), null);
+ cmi.mActionOneIcon = pluginResources.getDrawable(icpi.getActionOneIcon(), null);
+ cmi.mActionTwoIcon = pluginResources.getDrawable(icpi.getActionTwoIcon(), null);
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Resource Not found: " + cn);
+ mCallMethodInfos.remove(cn);
+ return;
+ }
+
+ cmi.mComponent = cn;
+ cmi.mName = icpi.getTitle();
+ cmi.mSummary = icpi.getSummary();
+ cmi.mSlotId = -1;
+ cmi.mSubId = -1;
+ cmi.mColor = NO_COLOR;
+ cmi.mSubscriptionButtonText = icpi.getSubscriptionButtonText();
+ cmi.mCreditButtonText = icpi.getCreditsButtonText();
+ cmi.mT9HintDescription = icpi.getT9HintDescription();
+ cmi.pluginResources = pluginResources;
+ cmi.mActionOneText = icpi.getActionOneTitle();
+ cmi.mActionTwoText = icpi.getActionTwoTitle();
+ cmi.mIsInCallProvider = true;
+
+ mCallMethodInfos.put(cn, cmi);
+ maybeBroadcastToSubscribers();
+ }
+ }
+ });
+ }
+
+ /**
+ * Get our plugin enabled status
+ * @param cn
+ */
+ private static void getCallMethodStatus(final ComponentName cn) {
+ getInstance().mInCallApi.getPluginStatus(getInstance().mClient, cn)
+ .setResultCallback(new ResultCallback<PluginStatusResult>() {
+ @Override
+ public void onResult(PluginStatusResult pluginStatusResult) {
+ synchronized (mCallMethodInfos) {
+ CallMethodInfo cmi = getCallMethodIfExists(cn);
+ if (cmi != null) {
+ cmi.mStatus = pluginStatusResult.status;
+ mCallMethodInfos.put(cn, cmi);
+ maybeBroadcastToSubscribers();
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Send an event to the component
+ * @param cn componentName to send the data to.
+ */
+ public static void shipAnalyticsToPlugin(final ComponentName cn, Event e) {
+ if (cn == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "componentName: " + cn.toShortString());
+ Log.d(TAG, "Event: " + e.toString());
+ }
+ getInstance().mInCallApi.sendAnalyticsEventToPlugin(getInstance().mClient, cn, e)
+ .setResultCallback(new ResultCallback<Result>() {
+ @Override
+ public void onResult(Result result) {
+ if (DEBUG) {
+ Log.v(TAG, "Event sent with result: " + result.getStatus().getStatusMessage());
+ }
+ }
+ });
+ }
+
+ /**
+ * Get the call method mime type
+ * @param cn
+ */
+ private static void getCallMethodMimeType(final ComponentName cn) {
+ getInstance().mInCallApi.getCallableMimeType(getInstance().mClient, cn)
+ .setResultCallback(new ResultCallback<MimeTypeResult>() {
+ @Override
+ public void onResult(MimeTypeResult mimeTypeResult) {
+ synchronized (mCallMethodInfos) {
+ CallMethodInfo cmi = getCallMethodIfExists(cn);
+ if (cmi != null) {
+ cmi.mMimeType = mimeTypeResult.mimeType;
+ mCallMethodInfos.put(cn, cmi);
+ maybeBroadcastToSubscribers();
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Get the Authentication state of the callmethod
+ * @param cn
+ */
+ private static void getCallMethodAuthenticated(final ComponentName cn,
+ final boolean dynamicRefresh) {
+ getInstance().mInCallApi.getAuthenticationState(getInstance().mClient, cn)
+ .setResultCallback(new ResultCallback<AuthenticationStateResult>() {
+ @Override
+ public void onResult(AuthenticationStateResult result) {
+ synchronized (mCallMethodInfos) {
+ CallMethodInfo cmi = getCallMethodIfExists(cn);
+ if (cmi != null) {
+ cmi.mIsAuthenticated = result.result == StatusCodes.AuthenticationState
+ .LOGGED_IN;
+ mCallMethodInfos.put(cn, cmi);
+ maybeBroadcastToSubscribers(dynamicRefresh);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Get the settings intent for the callmethod
+ * @param cn
+ */
+ private static void getSettingsIntent(final ComponentName cn) {
+ getInstance().mInCallApi.getSettingsIntent(getInstance().mClient, cn)
+ .setResultCallback(new ResultCallback<PendingIntentResult>() {
+ @Override
+ public void onResult(PendingIntentResult pendingIntentResult) {
+ synchronized (mCallMethodInfos) {
+ CallMethodInfo cmi = getCallMethodIfExists(cn);
+ if (cmi != null) {
+ cmi.mSettingsIntent = pendingIntentResult.intent;
+ mCallMethodInfos.put(cn, cmi);
+ maybeBroadcastToSubscribers();
+ }
+ }
+ }
+ });
+ }
+
+ private static void getCreditInfo(final ComponentName cn,
+ final boolean dynamicRefresh) {
+ // Let's attach a listener so that we can continue to listen to any credit changes
+ if (mCallCreditListeners.get(cn) == null) {
+ /* CallCreditListenerImpl listener = CallCreditListenerImpl.getInstance(cn);
+ getInstance().mInCallApi.addCreditListener(getInstance().mClient, cn, listener);
+ mCallCreditListeners.put(cn, listener); */
+ }
+ getInstance().mInCallApi.getCreditInfo(getInstance().mClient, cn)
+ .setResultCallback(new ResultCallback<GetCreditInfoResultResult>() {
+ @Override
+ public void onResult(GetCreditInfoResultResult getCreditInfoResultResult) {
+ CallMethodInfo cmi = getCallMethodIfExists(cn);
+ if (cmi != null) {
+ GetCreditInfoResult gcir = getCreditInfoResultResult.result;
+ if (gcir == null || gcir.creditInfo == null) {
+ // Build zero credit dummy if no result found.
+ cmi.mProviderCreditInfo =
+ new CreditInfo(new CreditBalance(0, null), null);
+ } else {
+ cmi.mProviderCreditInfo = gcir.creditInfo;
+ }
+ mCallMethodInfos.put(cn, cmi);
+ maybeBroadcastToSubscribers(dynamicRefresh);
+ }
+ }
+ });
+ }
+
+ private static void getManageCreditsIntent(final ComponentName cn) {
+ getInstance().mInCallApi.getManageCreditsIntent(getInstance().mClient, cn)
+ .setResultCallback(new ResultCallback<PendingIntentResult>() {
+ @Override
+ public void onResult(PendingIntentResult pendingIntentResult) {
+ CallMethodInfo cmi = getCallMethodIfExists(cn);
+ if (cmi != null) {
+ cmi.mManageCreditIntent = pendingIntentResult.intent;
+ mCallMethodInfos.put(cn, cmi);
+ maybeBroadcastToSubscribers();
+ }
+ }
+ });
+ }
+
+ private static void checkLowCreditConfig(final ComponentName cn) {
+ // find a nudge component if it exists for this package
+ Intent nudgeIntent = new Intent("cyanogen.service.NUDGE_PROVIDER");
+ nudgeIntent.setPackage(cn.getPackageName());
+ List<ResolveInfo> resolved = getInstance().mContext.getPackageManager()
+ .queryIntentServices(nudgeIntent, 0);
+ if (resolved != null && !resolved.isEmpty()) {
+ ResolveInfo result = resolved.get(0);
+ ComponentName nudgeComponent = new ComponentName(result.serviceInfo.applicationInfo
+ .packageName, result.serviceInfo.name);
+ collectLowCreditConfig(cn, nudgeComponent);
+ return;
+ }
+
+ // if a nudge component doesn't exist, just finish here
+ maybeBroadcastToSubscribers();
+ }
+
+ private static void collectLowCreditConfig(final ComponentName pluginComponent, final
+ ComponentName nudgeComponent) {
+ NudgeServices.NudgeApi.getConfigurationForKey(getInstance().mClient, nudgeComponent,
+ NudgeKey.INCALL_CREDIT_NUDGE).setResultCallback(new ResultCallback<BundleResult>() {
+ @Override
+ public void onResult(BundleResult bundleResult) {
+ CallMethodInfo cmi = getCallMethodIfExists(pluginComponent);
+ if (cmi != null) {
+ if (bundleResult != null && bundleResult.bundle != null &&
+ bundleResult.bundle.containsKey(NudgeKey
+ .INCALL_PARAM_CREDIT_WARN)) {
+ cmi.mCreditWarn = bundleResult.bundle.getFloat(NudgeKey
+ .INCALL_PARAM_CREDIT_WARN);
+ mCallMethodInfos.put(pluginComponent, cmi);
+ }
+ maybeBroadcastToSubscribers();
+ }
+ }
+ });
+ }
+}
diff --git a/src-ambient/incall/CallMethodInfo.java b/src-ambient/incall/CallMethodInfo.java
new file mode 100644
index 0000000..26f5018
--- /dev/null
+++ b/src-ambient/incall/CallMethodInfo.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2015 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.phone.common.incall;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+import com.android.phone.common.ambient.AmbientConnection;
+import com.android.phone.common.util.StartInCallCallReceiver;
+import com.cyanogen.ambient.incall.InCallServices;
+import com.cyanogen.ambient.incall.extension.CreditBalance;
+import com.cyanogen.ambient.incall.extension.CreditInfo;
+import com.cyanogen.ambient.incall.extension.StartCallRequest;
+import com.cyanogen.ambient.incall.extension.SubscriptionInfo;
+import com.google.common.base.Objects;
+
+import java.math.BigDecimal;
+import java.util.Currency;
+import java.util.List;
+
+public class CallMethodInfo {
+
+ public String mId;
+ public UserHandle mUserHandle;
+ public ComponentName mComponent;
+ public String mName;
+ public String mSummary;
+ public int mSlotId;
+ public int mSubId;
+ public int mColor;
+ public int mStatus;
+ public boolean mIsAuthenticated;
+ public String mMimeType;
+ public String mSubscriptionButtonText;
+ public String mCreditButtonText;
+ public String mT9HintDescription;
+ public PendingIntent mSettingsIntent;
+ public Drawable mBrandIcon;
+ public Drawable mBadgeIcon;
+ public Drawable mLoginIcon;
+ public Drawable mActionOneIcon;
+ public Drawable mActionTwoIcon;
+ public Resources pluginResources;
+ public String mActionOneText;
+ public String mActionTwoText;
+ public boolean mIsInCallProvider;
+ public PendingIntent mManageCreditIntent;
+ public CreditInfo mProviderCreditInfo;
+ public float mCreditWarn = 0.0f;
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mId, mUserHandle, mComponent, mName, mSummary, mSlotId, mSubId,
+ mColor, mStatus, mIsAuthenticated, mMimeType, mSubscriptionButtonText,
+ mCreditButtonText, mT9HintDescription, mSettingsIntent, mBrandIcon, mBadgeIcon,
+ mLoginIcon, mActionOneIcon, mActionTwoIcon, pluginResources, mActionOneText,
+ mActionTwoText, mIsInCallProvider);
+ }
+
+ public static final String TAG = "CallMethodInfo";
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (object instanceof CallMethodInfo) {
+ final CallMethodInfo info = (CallMethodInfo) object;
+ return Objects.equal(this.mId, info.mId)
+ && Objects.equal(this.mUserHandle, info.mUserHandle)
+ && Objects.equal(this.mComponent, info.mComponent)
+ && Objects.equal(this.mName, info.mName)
+ && Objects.equal(this.mSummary, info.mSummary)
+ && Objects.equal(this.mSlotId, info.mSlotId)
+ && Objects.equal(this.mSubId, info.mSubId)
+ && Objects.equal(this.mColor, info.mColor)
+ && Objects.equal(this.mStatus, info.mStatus)
+ && Objects.equal(this.mIsAuthenticated, info.mIsAuthenticated)
+ && Objects.equal(this.mMimeType, info.mMimeType)
+ && Objects.equal(this.mSubscriptionButtonText, info.mSubscriptionButtonText)
+ && Objects.equal(this.mCreditButtonText, info.mCreditButtonText)
+ && Objects.equal(this.mT9HintDescription, info.mT9HintDescription)
+ && Objects.equal(this.mSettingsIntent, info.mSettingsIntent)
+ && Objects.equal(this.mBrandIcon, info.mBrandIcon)
+ && Objects.equal(this.mBadgeIcon, info.mBadgeIcon)
+ && Objects.equal(this.mLoginIcon, info.mLoginIcon)
+ && Objects.equal(this.mActionOneIcon, info.mActionOneIcon)
+ && Objects.equal(this.mActionTwoIcon, info.mActionTwoIcon)
+ && Objects.equal(this.pluginResources, info.pluginResources)
+ && Objects.equal(this.mActionOneText, info.mActionOneText)
+ && Objects.equal(this.mActionTwoText, info.mActionTwoText)
+ && Objects.equal(this.mIsInCallProvider, info.mIsInCallProvider);
+ }
+ return false;
+ }
+
+ public void placeCall(String origin, String number, Context c) {
+ placeCall(origin, number, c, false);
+ }
+
+ public void placeCall(String origin, String number, Context c, boolean isVideoCall) {
+ StartInCallCallReceiver svcrr = CallMethodHelper.getVoIPResultReceiver(this, origin);
+ StartCallRequest request = new StartCallRequest(number, origin, 0, svcrr);
+
+ if (isVideoCall) {
+ InCallServices.getInstance().startVideoCall(
+ AmbientConnection.CLIENT.get(c), this.mComponent, request);
+ } else {
+ if (PhoneNumberUtils.isGlobalPhoneNumber(number)) {
+ InCallServices.getInstance().startOutCall(
+ AmbientConnection.CLIENT.get(c), this.mComponent, request);
+ } else {
+ InCallServices.getInstance().startVoiceCall(
+ AmbientConnection.CLIENT.get(c), this.mComponent, request);
+ }
+ }
+ }
+
+ private boolean isSubscription;
+ private boolean isCredits;
+ private int mCurrencyAmmount;
+
+ public String getCreditsDescriptionText(Resources r) {
+ String ret = null;
+ CreditInfo ci = this.mProviderCreditInfo;
+
+ List<SubscriptionInfo> subscriptionInfos = ci.subscriptions;
+
+ if (subscriptionInfos != null && !subscriptionInfos.isEmpty()) {
+ int subscriptionSize = subscriptionInfos.size();
+ StringBuilder subscripText = new StringBuilder();
+ int extraCount = 0;
+ for (int i = 0; i < subscriptionSize; i++) {
+ SubscriptionInfo si = subscriptionInfos.get(i);
+ if (i >= 3) {
+ extraCount++;
+ if (i == subscriptionSize - 1) {
+ subscripText.append("+" + extraCount);
+ }
+ } else {
+ // Region codes should be no larger than three char long the credits bar
+ // can only show so much.
+ if (si.regionCode.length() <= 3) {
+ subscripText.append(si.regionCode);
+ if (i < subscriptionSize - 1) {
+ subscripText.append(", ");
+ }
+ }
+ }
+ }
+ return subscripText.toString();
+ } else {
+ CreditBalance balance = ci.balance;
+ if (balance != null) {
+ mCurrencyAmmount = (int) balance.balance;
+ try {
+ if (balance.currencyCode != null) {
+ Currency currencyCode = Currency.getInstance(balance.currencyCode);
+ BigDecimal availableCredit = BigDecimal.valueOf(mCurrencyAmmount,
+ currencyCode.getDefaultFractionDigits());
+
+
+ return currencyCode.getSymbol(r.getConfiguration().locale)
+ + availableCredit.toString();
+ } else {
+ throw new IllegalArgumentException();
+ }
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Unable to retrieve currency code for plugin: " +
+ this.mComponent);
+
+ return null;
+ }
+ } else {
+ Log.e(TAG, "Plugin has credit component but the balance and subscriptions are" +
+ " null. This should never happen. Not showing credit banner due to " +
+ "failures.");
+
+ return null;
+ }
+ }
+ }
+
+ public boolean usesSubscriptions() {
+ return isSubscription;
+ }
+
+ public boolean usesCurrency() {
+ return isCredits;
+ }
+
+ public int getCurrencyAmount() {
+ return mCurrencyAmmount;
+ }
+}
diff --git a/src/com/android/phone/common/incall/CallMethodSpinnerAdapter.java b/src-ambient/incall/CallMethodSpinnerAdapter.java
index 434e13d..1aebddc 100644
--- a/src/com/android/phone/common/incall/CallMethodSpinnerAdapter.java
+++ b/src-ambient/incall/CallMethodSpinnerAdapter.java
@@ -28,7 +28,6 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
-import com.android.dialer.incall.CallMethodInfo;
import com.android.phone.common.R;
diff --git a/src-ambient/incall/CallMethodUtils.java b/src-ambient/incall/CallMethodUtils.java
new file mode 100644
index 0000000..f80a6ad
--- /dev/null
+++ b/src-ambient/incall/CallMethodUtils.java
@@ -0,0 +1,108 @@
+package com.android.phone.common.incall;
+
+import android.content.Context;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.phone.common.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.cyanogen.ambient.incall.util.InCallHelper.NO_COLOR;
+
+/**
+ * Basic Utils for call method modifications
+ */
+public class CallMethodUtils {
+
+ public final static String PREF_SPINNER_COACHMARK_SHOW = "pref_spinner_coachmark_shown";
+ public final static String PREF_LAST_ENABLED_PROVIDER = "pref_last_enabled_provider";
+ public final static String PREF_INTERNATIONAL_CALLS = "pref_international_calls";
+
+ /**
+ * Return whether the card in the given slot is activated
+ */
+ public static boolean isIccCardActivated(int slot) {
+ TelephonyManager tm = TelephonyManager.getDefault();
+ final int simState = tm.getSimState(slot);
+ return (simState != TelephonyManager.SIM_STATE_ABSENT)
+ && (simState != TelephonyManager.SIM_STATE_UNKNOWN);
+ }
+
+ public static List<CallMethodInfo> getSimInfoList(Context context) {
+ final TelecomManager telecomMgr =
+ (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+ final List<PhoneAccountHandle> accountHandles = telecomMgr.getCallCapablePhoneAccounts();
+ ArrayList<CallMethodInfo> callMethodInfoList = new ArrayList<CallMethodInfo>();
+ for (PhoneAccountHandle accountHandle : accountHandles) {
+ PhoneAccount phoneAccount = telecomMgr.getPhoneAccount(accountHandle);
+ CallMethodInfo callMethodInfo = new CallMethodInfo();
+ callMethodInfo.mComponent = accountHandle.getComponentName();
+ callMethodInfo.mId = accountHandle.getId();
+ callMethodInfo.mUserHandle = accountHandle.getUserHandle();
+ callMethodInfo.mColor = getPhoneAccountColor(phoneAccount);
+ callMethodInfo.mSubId = Integer.parseInt(accountHandle.getId());
+ callMethodInfo.mSlotId = SubscriptionManager.getSlotId(callMethodInfo.mSubId);
+ callMethodInfo.mName =
+ getPhoneAccountName(context, phoneAccount, callMethodInfo.mSlotId);
+ callMethodInfo.mIsInCallProvider = false;
+ if (isIccCardActivated(callMethodInfo.mSlotId)) {
+ callMethodInfoList.add(callMethodInfo);
+ }
+ }
+ return callMethodInfoList;
+ }
+
+ private static String getPhoneAccountName(Context context, PhoneAccount phoneAccount,
+ int slotId) {
+ if (phoneAccount == null) {
+ return context.getString(R.string.call_method_spinner_item_unknown_sim, slotId + 1);
+ }
+ return phoneAccount.getLabel().toString();
+ }
+
+ private static int getPhoneAccountColor(PhoneAccount phoneAccount) {
+ if (phoneAccount == null) {
+ return NO_COLOR;
+ }
+
+ int highlightColor = phoneAccount.getHighlightColor();
+
+ if (highlightColor != PhoneAccount.NO_HIGHLIGHT_COLOR) {
+ return highlightColor;
+ } else {
+ return NO_COLOR;
+ }
+ }
+
+ public static CallMethodInfo getDefaultDataSimInfo(Context context) {
+ SubscriptionManager subMgr = SubscriptionManager.from(context);
+ SubscriptionInfo subInfo = subMgr.getActiveSubscriptionInfo(
+ SubscriptionManager.getDefaultSubId());
+
+ if (subInfo == null) return null;
+
+ CallMethodInfo callMethodInfo = new CallMethodInfo();
+ callMethodInfo.mSubId = subInfo.getSubscriptionId();
+ callMethodInfo.mColor = subInfo.getIconTint();
+ callMethodInfo.mName = subInfo.getDisplayName().toString();
+ callMethodInfo.mSlotId = SubscriptionManager.getSlotId(callMethodInfo.mSubId);
+
+ final TelecomManager telecomMgr =
+ (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+ final List<PhoneAccountHandle> accountHandles = telecomMgr.getCallCapablePhoneAccounts();
+ for (PhoneAccountHandle accountHandle : accountHandles) {
+ if (callMethodInfo.mSubId == Integer.parseInt(accountHandle.getId())) {
+ callMethodInfo.mComponent = accountHandle.getComponentName();
+ break;
+ }
+ }
+
+ return callMethodInfo;
+ }
+}