diff options
author | Stephen Bird <sbird@cyngn.com> | 2016-01-21 19:04:13 -0800 |
---|---|---|
committer | Richard MacGregor <rmacgregor@cyngn.com> | 2016-04-08 10:42:49 -0700 |
commit | a6049e69f130b9f5557465315112c5ca6647b3b9 (patch) | |
tree | 11d9eb75ea2a396cc33a489bc6a3755b31bdd5e3 | |
parent | c5f5b115f589f95d02fb0fd3690d098ca6397974 (diff) | |
download | android_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.mk | 1 | ||||
-rw-r--r-- | res/values/cm_strings.xml | 3 | ||||
-rw-r--r-- | src-ambient/ambient/AmbientConnection.java | 66 | ||||
-rw-r--r-- | src-ambient/ambient/SingletonHolder.java | 50 | ||||
-rw-r--r-- | src-ambient/incall/CallMethodHelper.java | 694 | ||||
-rw-r--r-- | src-ambient/incall/CallMethodInfo.java | 214 | ||||
-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.java | 108 |
8 files changed, 1136 insertions, 1 deletions
@@ -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<FooSingleton, ParamObject> HOLDER = + * new SingletonHolder<FooSingleton, ParamObject>() { + * @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; + } +} |