summaryrefslogtreecommitdiffstats
path: root/src-ambient/com
diff options
context:
space:
mode:
Diffstat (limited to 'src-ambient/com')
-rw-r--r--src-ambient/com/android/phone/common/ambient/AmbientConnection.java65
-rw-r--r--src-ambient/com/android/phone/common/ambient/AmbientDataSubscription.java326
-rw-r--r--src-ambient/com/android/phone/common/ambient/SingletonHolder.java56
-rw-r--r--src-ambient/com/android/phone/common/ambient/TypedPendingResult.java53
-rw-r--r--src-ambient/com/android/phone/common/ambient/utils/PluginUtils.java26
-rw-r--r--src-ambient/com/android/phone/common/incall/CallMethodInfo.java357
-rw-r--r--src-ambient/com/android/phone/common/incall/CallMethodSpinnerAdapter.java304
-rw-r--r--src-ambient/com/android/phone/common/incall/ContactsDataSubscription.java123
-rw-r--r--src-ambient/com/android/phone/common/incall/CreditBarHelper.java105
-rw-r--r--src-ambient/com/android/phone/common/incall/DialerDataSubscription.java193
-rw-r--r--src-ambient/com/android/phone/common/incall/StartInCallCallReceiver.java62
-rw-r--r--src-ambient/com/android/phone/common/incall/api/ApiHelper.java38
-rw-r--r--src-ambient/com/android/phone/common/incall/api/InCallListeners.java84
-rw-r--r--src-ambient/com/android/phone/common/incall/api/InCallQueries.java230
-rw-r--r--src-ambient/com/android/phone/common/incall/api/InCallResults.java283
-rw-r--r--src-ambient/com/android/phone/common/incall/listeners/AuthenticationListenerImpl.java58
-rw-r--r--src-ambient/com/android/phone/common/incall/listeners/CallCreditListenerImpl.java53
-rw-r--r--src-ambient/com/android/phone/common/incall/utils/CallMethodFilters.java94
-rw-r--r--src-ambient/com/android/phone/common/incall/utils/CallMethodUtils.java220
-rw-r--r--src-ambient/com/android/phone/common/incall/utils/MimeTypeUtils.java143
-rw-r--r--src-ambient/com/android/phone/common/nudge/api/ApiHelper.java31
-rw-r--r--src-ambient/com/android/phone/common/nudge/api/NudgeQueries.java55
-rw-r--r--src-ambient/com/android/phone/common/nudge/api/NudgeTypedResult.java51
-rw-r--r--src-ambient/com/android/phone/common/nudge/utils/NudgeUtils.java47
24 files changed, 3057 insertions, 0 deletions
diff --git a/src-ambient/com/android/phone/common/ambient/AmbientConnection.java b/src-ambient/com/android/phone/common/ambient/AmbientConnection.java
new file mode 100644
index 0000000..04fe63a
--- /dev/null
+++ b/src-ambient/com/android/phone/common/ambient/AmbientConnection.java
@@ -0,0 +1,65 @@
+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 = "PhoneCommon.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, "Connection failed: " + result);
+ }
+ });
+ client.registerDisconnectionListener(
+ new AmbientApiClient.OnDisconnectionListener() {
+ @Override
+ public void onDisconnection() {
+ Log.d(TAG, "Connection disconnected");
+ }
+ });
+ client.registerConnectionCallbacks(
+ new AmbientApiClient.ConnectionCallbacks() {
+ @Override
+ public void onConnected(Bundle connectionHint) {
+ Log.d(TAG, "Connection established");
+ }
+
+ @Override
+ public void onConnectionSuspended(int cause) {
+ Log.d(TAG, "Connection suspended");
+ }
+ });
+ client.connect();
+ return client;
+ }
+ };
+
+}
diff --git a/src-ambient/com/android/phone/common/ambient/AmbientDataSubscription.java b/src-ambient/com/android/phone/common/ambient/AmbientDataSubscription.java
new file mode 100644
index 0000000..3b30ccb
--- /dev/null
+++ b/src-ambient/com/android/phone/common/ambient/AmbientDataSubscription.java
@@ -0,0 +1,326 @@
+/*
+ * 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.phone.common.ambient;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import com.cyanogen.ambient.common.api.AmbientApiClient;
+import com.cyanogen.ambient.common.api.PendingResult;
+import com.cyanogen.ambient.common.api.Result;
+import com.cyanogen.ambient.common.api.ResultCallback;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper method for holding and broadcasting updated plugin data
+ *
+ */
+public abstract class AmbientDataSubscription<M> {
+
+ private static final String TAG = AmbientDataSubscription.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ // Our ambient client
+ public AmbientApiClient mClient;
+ public Context mContext;
+
+ // The handler that processes broadcasts
+ private Handler mMainHandler;
+
+ // Holder for plugin object
+ private HashMap<ComponentName, M> mPluginInfo;
+
+ private static boolean mDataHasBeenBroadcastPreviously = false;
+
+ // A list of our registered clients, who all register with the CallMethodReceiver
+ private static HashMap<String, PluginChanged> mRegisteredClients = new HashMap<>();
+
+ // A map of our components and if they have have enabled IInterface listeners. This is to keep
+ // track of if listeners should be added or removed.
+ private static HashMap<ComponentName, Boolean> mEnabledListeners = new HashMap<>();
+
+ // Wait up to 60 seconds for data to return. Mimicks what PluginBinderManager does.
+ private static final long TIMEOUT_MILLISECONDS = 60000L;
+
+ // Bootstrap is the initial callback to get your installed plugins.
+ // this is the only item that executes ASAP and has no componentname tied to it
+ public ResultCallback BOOTSTRAP = new ResultCallback<Result>() {
+
+ @Override
+ public void onResult(Result result) {
+ List<ComponentName> installedPlugins = getPluginComponents(result);
+ for (ComponentName cn : installedPlugins) {
+ ArrayList<TypedPendingResult> apiCallbacks = new ArrayList<>();
+ getPluginInfo().put(cn, getNewModObject(cn));
+ requestedModInfo(apiCallbacks, cn);
+ executeAll(apiCallbacks, cn);
+ }
+ }
+
+ };
+
+ public AmbientDataSubscription(Context context) {
+ mContext = context;
+ mClient = AmbientConnection.CLIENT.get(context);
+ mMainHandler = new Handler(context.getMainLooper());
+ mPluginInfo = new HashMap<>();
+ }
+
+ public interface PluginChanged<M> {
+ void onChanged(HashMap<ComponentName, M> pluginInfo);
+ }
+
+ /**
+ * OnPostResult is always called on the main thread (like onPostExecute in an asynctask)
+ *
+ * @param plugin The object that represents the plugin
+ * @param result the result of the resultCallback
+ * @param type the type defined by the TypedPendingResult that was sent to the apiexecutor
+ */
+ protected abstract void onPostResult(M plugin, Result result, int type);
+
+ /**
+ * Callback that gets the initial list of componentnames that are valid plugin for us to query
+ *
+ * @param result result of the initial call
+ * @return list of componentnames
+ */
+ protected abstract List<ComponentName> getPluginComponents(Result result);
+
+ /**
+ * Gets the required queries for all our plugins
+ *
+ * @param queries ArrayList for us to add TypedPendingResults to
+ * @param componentName ComponentName of the plugin we will be querying
+ */
+ protected abstract void requestedModInfo(ArrayList<TypedPendingResult> queries,
+ ComponentName componentName);
+
+
+ /**
+ * Action that takes place when a refresh is requested.
+ */
+ protected abstract void onRefreshRequested();
+
+ /**
+ * Called when dynamic items need to be refreshed. Dynamic items being things that may change
+ * over time in a plugin.
+ *
+ * @param apiCallbacks callbacks to add to the queue
+ * @param componentName of plugin that needs to be updated
+ */
+ protected abstract void onDynamicRefreshRequested(ArrayList<TypedPendingResult> apiCallbacks,
+ ComponentName componentName);
+
+ /**
+ * Methods to enable and disable listeners on a plugin
+ *
+ * @param plugin object that needs to be listened to.
+ */
+ protected abstract void enableListeners(M plugin);
+ protected abstract void disableListeners(M plugin);
+
+ /**
+ * Gets a new plugin object
+ *
+ * @param componentName of the plugin
+ * @return M object
+ */
+ protected abstract M getNewModObject(ComponentName componentName);
+
+ private void enablePluginListeners(ComponentName cn) {
+ if (!mEnabledListeners.containsKey(cn) || !mEnabledListeners.get(cn)) {
+ M plugin = getPluginIfExists(cn);
+ if (plugin != null) {
+ enableListeners(plugin);
+ mEnabledListeners.put(cn, true);
+ }
+
+ }
+ }
+
+ private void disablePluginListeners(ComponentName cn) {
+ if (mEnabledListeners.containsKey(cn) && mEnabledListeners.get(cn)) {
+ M plugin = getPluginIfExists(cn);
+ disableListeners(plugin);
+ mEnabledListeners.put(cn, false);
+ }
+ }
+
+ /**
+ * Broadcasts mModInfo to all registered clients on the Main thread.
+ */
+ public void broadcast() {
+ mMainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "broadcast");
+ for (PluginChanged client : mRegisteredClients.values()) {
+ client.onChanged(mPluginInfo);
+ }
+ }
+ });
+ }
+
+ /***
+ * Registers the client, on register returns boolean if plugin info 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 boolean subscribe(String id, PluginChanged cmr) {
+ mRegisteredClients.put(id, cmr);
+ if (DEBUG) Log.v("TAG", "subscribed: " + id);
+ return mDataHasBeenBroadcastPreviously;
+ }
+
+ /**
+ * Unsubscribes the client. All clients must unsubscribe when the client ends.
+ *
+ * @param id of the client to remove
+ */
+ public void unsubscribe(String id) {
+ mRegisteredClients.remove(id);
+ if (mRegisteredClients.isEmpty()) {
+ for (ComponentName cn : mPluginInfo.keySet()) {
+ disablePluginListeners(cn);
+ }
+ }
+ }
+
+ /**
+ * Refreshes certain items that can constantly change.
+ */
+ public void refreshDynamicItems() {
+ for (ComponentName cn : mPluginInfo.keySet()) {
+ ArrayList<TypedPendingResult> apiCallbacks = new ArrayList<>();
+ onDynamicRefreshRequested(apiCallbacks, cn);
+ executeAll(apiCallbacks, cn);
+ }
+ }
+
+ private void executeAll(final ArrayList<TypedPendingResult> apiCallbacks,
+ final ComponentName componentName) {
+
+ final ArrayList<PendingResult> pendingResults = new ArrayList<>();
+
+ for (final TypedPendingResult pendingResult : apiCallbacks) {
+ pendingResult.setResultCallback(new ResultCallback() {
+ @Override
+ public void onResult(Result result) {
+ pendingResults.remove(pendingResult.mPendingResult);
+ if (result == null) {
+ // Our plugin failed to make this call, log out what and why
+ Log.e(TAG, "Result from: " + componentName.getPackageName() + " failed");
+ } else {
+ if (result.getStatus().isSuccess()) {
+ M plugin = getPluginIfExists(componentName);
+ onPostResult(plugin, result, pendingResult.mType);
+
+ // check to see if our onPostResult removed the plugin.
+ if (!getPluginInfo().containsKey(componentName)) {
+ // Our plugin no longer exists for some reason, clear other callbacks
+ // and broadcast changes.
+ for (PendingResult pr : pendingResults) {
+ pr.cancel();
+ }
+ pendingResults.clear();
+ }
+ } else if (result.getStatus().isCanceled()) {
+ // Our plugin was found invalid or no longer exists.
+ Log.e(TAG, "Queries to: " + componentName.getPackageName()
+ + " are cancelled");
+ } else {
+ Log.e(TAG, "Query to: " + componentName.getPackageName() + " "
+ + result.getClass().getSimpleName() + " failed. Code: " +
+ result.getStatus().getStatusMessage());
+ }
+ }
+ maybeBroadcastToSubscribers(pendingResults);
+
+ }
+ }, TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
+
+ // Add items that we've already added to our queue for so we can cancel them if
+ // need be. Set resultcallback will return instantly but onResult will not.
+ // So we can use this as our "counter" to determine if we should broadcast
+ pendingResults.add(pendingResult.mPendingResult);
+ }
+ enablePluginListeners(componentName);
+ }
+
+ public void refresh() {
+ onRefreshRequested();
+ }
+
+ public static boolean infoReady() {
+ return mDataHasBeenBroadcastPreviously;
+ }
+
+ private void maybeBroadcastToSubscribers(ArrayList<PendingResult> apiCallbacks) {
+ maybeBroadcastToSubscribers(apiCallbacks, false);
+ }
+
+ /**
+ * 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 ModCore. 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.
+ *
+ * @param apiCallbacks hashmap of our callbacks and pendingresults
+ * @param critical marks this call as critical to shortcircut our general broadcast and
+ * broadcast right away.
+ */
+ private void maybeBroadcastToSubscribers(ArrayList<PendingResult> apiCallbacks,
+ boolean critical) {
+
+ if (apiCallbacks.isEmpty() || critical) {
+ // we are on the last item or we are a critical item. broadcast updated hashmap
+ broadcast();
+ // We don't want to tell providers that our main data has already been broadcast
+ // if it's just a critical broadcast to prevent extra work from our subscribers
+ // once our long running calls are broadcast then this will be true.
+ if (!critical) {
+ mDataHasBeenBroadcastPreviously = true;
+ }
+ }
+ }
+
+ /**
+ * 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 M if valid, otherwise null
+ */
+ public M getPluginIfExists(ComponentName cn) {
+ return getPluginInfo().get(cn);
+ }
+
+ public HashMap<ComponentName, M> getPluginInfo() {
+ return mPluginInfo;
+ }
+}
diff --git a/src-ambient/com/android/phone/common/ambient/SingletonHolder.java b/src-ambient/com/android/phone/common/ambient/SingletonHolder.java
new file mode 100644
index 0000000..bca6023
--- /dev/null
+++ b/src-ambient/com/android/phone/common/ambient/SingletonHolder.java
@@ -0,0 +1,56 @@
+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;
+ }
+
+ public final boolean isCreated() {
+ synchronized (LOCK) {
+ return mInstance != null;
+ }
+ }
+
+ protected abstract E create(I initializer);
+}
diff --git a/src-ambient/com/android/phone/common/ambient/TypedPendingResult.java b/src-ambient/com/android/phone/common/ambient/TypedPendingResult.java
new file mode 100644
index 0000000..783b519
--- /dev/null
+++ b/src-ambient/com/android/phone/common/ambient/TypedPendingResult.java
@@ -0,0 +1,53 @@
+package com.android.phone.common.ambient;
+
+import com.cyanogen.ambient.common.api.PendingResult;
+import com.cyanogen.ambient.common.api.ResultCallback;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * PendingResult wrapped with a Type
+ *
+ * Some queries return the same pending result type. We need a way to differentiate these items to
+ * know how to process them.
+ */
+public class TypedPendingResult {
+
+ PendingResult mPendingResult;
+ int mType;
+
+ // None should be used when a unique Result will be returned from our ResultCallback.
+ public static final int NONE = 0;
+
+ // 1 - 1000 Reserved for INCALL API
+ public static final int GENERAL_MIME_TYPE = 1;
+ public static final int IM_MIME_TYPE = 2;
+ public static final int VIDEO_MIME_TYPE = 3;
+
+ public static final int SETTINGS_INTENT = 4;
+ public static final int CREDIT_INTENT = 5;
+ public static final int LOGIN_INTENT = 6;
+ public static final int DEFAULT_DIRECTORY_SEARCH_INTENT = 7;
+
+ public static final int GENERAL_DATA = 8;
+ public static final int STATUS = 9;
+ public static final int AUTHENTICATION = 10;
+ public static final int CREDIT_INFO = 11;
+ public static final int ACCOUNT_HANDLE = 12;
+ public static final int HINT_TEXT = 13;
+
+ // 1001 - 2000 Reserved for Nudges
+ public static final int INCALL_CREDIT_NUDGE = 1001;
+ public static final int INCALL_CONTACT_CARD_LOGIN = 1002;
+ public static final int INCALL_CONTACT_CARD_DOWNLOAD = 1003;
+ public static final int INCALL_CONTACT_FRAGMENT_LOGIN = 1004;
+
+ public TypedPendingResult(PendingResult pendingResult, int type) {
+ this.mPendingResult = pendingResult;
+ this.mType = type;
+ }
+
+ public void setResultCallback(ResultCallback resultCallback, long length, TimeUnit unit) {
+ this.mPendingResult.setResultCallback(resultCallback, length, unit);
+ }
+}
diff --git a/src-ambient/com/android/phone/common/ambient/utils/PluginUtils.java b/src-ambient/com/android/phone/common/ambient/utils/PluginUtils.java
new file mode 100644
index 0000000..471457b
--- /dev/null
+++ b/src-ambient/com/android/phone/common/ambient/utils/PluginUtils.java
@@ -0,0 +1,26 @@
+package com.android.phone.common.ambient.utils;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.util.Log;
+
+/**
+ * Utilites for Plugins
+ */
+public class PluginUtils {
+
+ private static final String TAG = PluginUtils.class.getSimpleName();
+
+ public static Resources getPluginResources(Context context, ComponentName componentName) {
+ PackageManager packageManager = context.getPackageManager();
+ try {
+ return packageManager.getResourcesForApplication(componentName.getPackageName());
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Plugin isn't installed: " + componentName.flattenToShortString());
+ return null;
+ }
+ }
+
+}
diff --git a/src-ambient/com/android/phone/common/incall/CallMethodInfo.java b/src-ambient/com/android/phone/common/incall/CallMethodInfo.java
new file mode 100644
index 0000000..cbc5242
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/CallMethodInfo.java
@@ -0,0 +1,357 @@
+/*
+ * 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.provider.ContactsContract;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.phone.common.ambient.AmbientConnection;
+import com.android.phone.common.R;
+import com.android.phone.common.incall.listeners.AuthenticationListenerImpl;
+import com.android.phone.common.incall.listeners.CallCreditListenerImpl;
+import com.android.phone.common.incall.utils.CallMethodUtils;
+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 mIsInCallProvider;
+ public boolean mIsAuthenticated;
+ public String mMimeType;
+ public String mVideoCallableMimeType;
+ public String mImMimeType;
+ public String mSubscriptionButtonText;
+ public String mCreditButtonText;
+ public String mT9HintDescriptionNoCreditOrSub;
+ public String mT9HintDescriptionHasCreditOrSub;
+ /* Plugin's simple brand icon (24dp x 24dp)
+ Expected format: Vector Drawable (.xml)
+ 2 colors allowed. */
+ public Drawable mBrandIcon;
+ /* Plugin's single color simple brand icon (24dp x 24dp)
+ Same as to pluginBrandIcon, but only a single color.
+ Please keep image single color, it will be tinted in the client application.
+ Expected format: Vector Drawable (.xml)
+ 1 color allowed. */
+ public Drawable mSingleColorBrandIcon;
+ /* Plugin's attribution badge (17dp x 17dp)
+ Similar to pluginBrandIcon.
+ To make badge pop against (on top of) images, please add a white outline.
+ Expected format: Vector Drawable (.xml)
+ 2 colors allowed. */
+ public Drawable mBadgeIcon;
+ /* Plugin's full brand icon (any width (max 360dp) x 60dp height)
+ May contain full brand name or icon image.
+ Please keep image single color, it will be tinted in the client application.
+ Expected format: Vector Drawable (.xml)
+ 1 color allowed. */
+ public Drawable mLoginIcon;
+ /* Plugin's video call action icon (24dp x 24dp)
+ Expected format: Vector Drawable (.xml)
+ 1 color allowed. */
+ public Drawable mVideoIcon;
+ /* Plugin's IM action icon (24dp x 24dp)
+ Expected format: Vector Drawable (.xml)
+ 1 color allowed. */
+ public Drawable mImIcon;
+ /* Plugin's voice call action icon (24dp x 24dp)
+ Expected format: Vector Drawable (.xml)
+ 1 color allowed. */
+ public Drawable mVoiceIcon;
+ public CreditInfo mProviderCreditInfo;
+ public float mCreditWarn = 0.0f;
+ private int mCurrencyAmount;
+ private CallCreditListenerImpl mCreditListener;
+ public PendingIntent mManageCreditIntent;
+ public PendingIntent mLoginIntent;
+ public PendingIntent mDefaultDirectorySearchIntent; // empty contact Uri
+ public PendingIntent mDirectorySearchIntent;
+ public PendingIntent mInviteIntent;
+ public PendingIntent mSettingsIntent;
+ private static CallMethodInfo sEmergencyCallMethod;
+ private AuthenticationListenerImpl mAuthListener;
+ public String mAccountType;
+ public String mDependentPackage;
+ public String mLoginSubtitle;
+ public String mAccountHandle;
+ public int mLoginIconId; // resource ID
+ public int mBrandIconId; // resource ID
+ // Contact install nudge
+ public boolean mInstallNudgeEnable;
+ public String mInstallNudgeSubtitle;
+ public String mInstallNudgeActionText;
+ // Contact login nudge
+ public boolean mLoginNudgeEnable;
+ public String mLoginNudgeSubtitle;
+ public String mLoginNudgeActionText;
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mId, mComponent, mName, mSlotId, mSubId);
+ }
+
+ 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.mComponent, info.mComponent)
+ && Objects.equal(this.mName, info.mName)
+ && Objects.equal(this.mSlotId, info.mSlotId)
+ && Objects.equal(this.mSubId, info.mSubId);
+ }
+ return false;
+ }
+
+ /**
+ * return empty mock call method that represents emergency call only mode
+ */
+ public static CallMethodInfo getEmergencyCallMethod(Context ctx) {
+ if (sEmergencyCallMethod == null) {
+ sEmergencyCallMethod = new CallMethodInfo();
+ sEmergencyCallMethod.mName =
+ ctx.getString(R.string.call_method_spinner_item_emergency_call);
+ sEmergencyCallMethod.mColor =
+ ctx.getResources().getColor(R.color.emergency_call_icon_color);
+ sEmergencyCallMethod.mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ sEmergencyCallMethod.mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+ sEmergencyCallMethod.mIsInCallProvider = false;
+ }
+
+ return sEmergencyCallMethod;
+ }
+
+ public static PhoneAccountHandle getPhoneAccountHandleFromCallMethodInfo(Context ctx,
+ CallMethodInfo callMethodInfo, String number) {
+ CallMethodInfo emergencyCallMethod = getEmergencyCallMethod(ctx);
+ // If no sim is selected, or emergency callmethod selected, or number is
+ // an emergency number, phone account handle should be null, and will use the
+ // default account.
+ // Else, create PhoneAccountHandle from selected callmethod components and
+ // initial call using that account.
+ PhoneAccountHandle handle = null;
+ if (callMethodInfo != null && !callMethodInfo.mIsInCallProvider &&
+ !callMethodInfo.equals(emergencyCallMethod) &&
+ (number == null || !PhoneNumberUtils.isEmergencyNumber(number))) {
+ handle = new PhoneAccountHandle(callMethodInfo.mComponent,
+ callMethodInfo.mId,
+ callMethodInfo.mUserHandle);
+ }
+ return handle;
+ }
+
+ 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) {
+ placeCall(origin, number, c, isVideoCall, false, null, null);
+ }
+
+ public void placeCall(String origin, String number, Context c, boolean isVideoCall, boolean
+ forcePSTN) {
+ placeCall(origin, number, c, isVideoCall, forcePSTN, null, null);
+ }
+
+ public void placeCall(String origin, String number, Context c, boolean isVideoCall, boolean
+ forcePSTN, StartInCallCallReceiver.InCallCallListener listener) {
+ placeCall(origin, number, c, isVideoCall, forcePSTN, null, listener);
+ }
+
+ public void placeCall(String origin, String number, Context c, boolean isVideoCall,
+ boolean forcePSTN, String numberMimeType) {
+ placeCall(origin, number, c, isVideoCall, forcePSTN, numberMimeType, null);
+ }
+
+ public void placeCall(String origin, String number, Context c, boolean isVideoCall,
+ boolean forcePSTN, String numberMimeType,
+ StartInCallCallReceiver.InCallCallListener listener) {
+
+ StartInCallCallReceiver svcrr
+ = CallMethodUtils.getVoIPResultReceiver(c, this, origin, listener);
+ StartCallRequest request = new StartCallRequest(number, origin, 0, svcrr);
+
+ if (isVideoCall) {
+ InCallServices.getInstance().startVideoCall(
+ AmbientConnection.CLIENT.get(c), this.mComponent, request);
+ } else {
+ if (forcePSTN || (numberMimeType != null && numberMimeType.equals(ContactsContract
+ .CommonDataKinds.Phone.CONTENT_ITEM_TYPE))) {
+ InCallServices.getInstance().startOutCall(
+ AmbientConnection.CLIENT.get(c), this.mComponent, request);
+ } else {
+ InCallServices.getInstance().startVoiceCall(
+ AmbientConnection.CLIENT.get(c), this.mComponent, request);
+ }
+ }
+ }
+
+ public String getCreditsDescriptionText(Resources r) {
+ CreditInfo ci = this.mProviderCreditInfo;
+
+ List<SubscriptionInfo> subscriptionInfos = ci.subscriptions;
+
+ if (showSubscriptions()) {
+ int size = subscriptionInfos.size();
+ return r.getQuantityString(R.plurals.number_of_incall_subscriptions, size, size);
+ } else {
+ CreditBalance balance = ci.balance;
+ if (balance != null) {
+ mCurrencyAmount = (int) balance.balance;
+ try {
+ if (balance.currencyCode != null) {
+ Currency currencyCode = Currency.getInstance(balance.currencyCode);
+ BigDecimal availableCredit = BigDecimal.valueOf(mCurrencyAmount,
+ 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 hasAvailableCredit() {
+ boolean hasAvailableCredit = false;
+ CreditInfo ci = this.mProviderCreditInfo;
+ if (ci != null) {
+ CreditBalance balance = ci.balance;
+ if (balance != null) {
+ mCurrencyAmount = (int) balance.balance;
+ hasAvailableCredit = mCurrencyAmount > 0;
+ }
+ }
+ return hasAvailableCredit;
+ }
+
+ public boolean hasSubscription() {
+ boolean hasSubscription = false;
+ CreditInfo ci = this.mProviderCreditInfo;
+ if (ci != null) {
+ List<SubscriptionInfo> subscriptionInfos = ci.subscriptions;
+ hasSubscription = subscriptionInfos != null && !subscriptionInfos.isEmpty();
+ }
+ return hasSubscription;
+ }
+
+ public boolean showSubscriptions() {
+ // If the user has > 0 credits, we don't want to show the subscription.
+ boolean hasCredits = hasAvailableCredit();
+ boolean hasSubscriptions = hasSubscription();
+
+ return !hasCredits && hasSubscriptions;
+ }
+
+ public int getCurrencyAmount() {
+ return mCurrencyAmount;
+ }
+
+ /**
+ * Returns hint text based on credit and subscription availability.
+ * If either is null or empty, use the non-null/non-empty string.
+ * Null is returned if both are null or empty.
+ * @return hintText String hint text for the current situation
+ */
+ public String getHintText() {
+ String hintText = null;
+ boolean hasAvailableCredit = hasAvailableCredit();
+ boolean usesSubscriptions = hasSubscription();
+ if (!hasAvailableCredit && !usesSubscriptions) {
+ // No credits and no subscription
+ if (!TextUtils.isEmpty(mT9HintDescriptionNoCreditOrSub)) {
+ // Use no credits or subscription hint
+ hintText = mT9HintDescriptionNoCreditOrSub;
+ } else if (!TextUtils.isEmpty(mT9HintDescriptionHasCreditOrSub)) {
+ // If no credit/sub hint empty, but has credit/sub hint valid, use it
+ hintText = mT9HintDescriptionHasCreditOrSub;
+ }
+ } else {
+ // Has credits or subscription
+ if (!TextUtils.isEmpty(mT9HintDescriptionHasCreditOrSub)) {
+ // Use has credits/sub hint text
+ hintText = mT9HintDescriptionHasCreditOrSub;
+ } else if (!TextUtils.isEmpty(mT9HintDescriptionNoCreditOrSub)) {
+ // If has credit/sub hint empty, but no credit/sub hint valid, use it
+ hintText = mT9HintDescriptionNoCreditOrSub;
+ }
+ }
+ return hintText;
+ }
+
+ public CallCreditListenerImpl getCreditListener() {
+ return mCreditListener;
+ }
+
+ public AuthenticationListenerImpl getAuthListener() {
+ return mAuthListener;
+ }
+
+ public void setCreditListener(CallCreditListenerImpl creditListener) {
+ mCreditListener = creditListener;
+ }
+
+ public void setAuthListener(AuthenticationListenerImpl authListener) {
+ mAuthListener = authListener;
+ }
+
+}
diff --git a/src-ambient/com/android/phone/common/incall/CallMethodSpinnerAdapter.java b/src-ambient/com/android/phone/common/incall/CallMethodSpinnerAdapter.java
new file mode 100644
index 0000000..cb3a8fe
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/CallMethodSpinnerAdapter.java
@@ -0,0 +1,304 @@
+/*
+ * 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.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.SpinnerAdapter;
+import android.widget.TextView;
+
+import com.android.phone.common.R;
+import com.android.phone.common.util.VolteUtils;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.cyanogen.ambient.incall.util.InCallHelper.NO_COLOR;
+
+public class CallMethodSpinnerAdapter extends ArrayAdapter<CallMethodInfo>
+ implements SpinnerAdapter {
+
+ private static final String TAG = CallMethodSpinnerAdapter.class.getSimpleName();
+ public static final int POSITION_UNKNOWN = -1;
+
+ private final Context mContext;
+ private Map<String, Integer> mComponentMap;
+ private boolean mShowVolte = false;
+
+ public CallMethodSpinnerAdapter(Context context, List<CallMethodInfo> objects, boolean
+ showVolte) {
+ super(context, 0, objects);
+ mContext = context.getApplicationContext();
+ processCallMethods(objects);
+ mShowVolte = showVolte;
+ }
+
+ /**
+ * Remove all elements from the list.
+ */
+ @Override
+ public void clear() {
+ super.clear();
+ if (mComponentMap != null) {
+ mComponentMap.clear();
+ }
+ }
+
+ /**
+ * Adds the specified call method info at the end of the array.
+ *
+ * @param object The call method info entry to add at the end of the array.
+ */
+ public void add(CallMethodInfo object) {
+ super.add(object);
+ processCallMethod(object);
+ }
+
+ /**
+ * Adds the specified list of call method infos at the end of the array.
+ *
+ * @param objects The list of call method info entries to add at the end of the array.
+ */
+ public void addAll(List<CallMethodInfo> objects) {
+ super.addAll(objects);
+ processCallMethods(objects);
+ }
+
+ /**
+ * Adds the specified list of call method infos at the end of the array.
+ *
+ * @param objects The list of call method info entries to add at the end of the array.
+ */
+ public void addAll(HashMap<ComponentName, CallMethodInfo> objects) {
+ super.addAll(objects.values());
+ processCallMethods(objects.values());
+ }
+
+ /**
+ * Get a View that displays the data at the specified position in the data set. You can either
+ * create a View manually or inflate it from an XML layout file. When the View is inflated, the
+ * parent View (GridView, ListView...) will apply default layout parameters unless you use
+ * {@link LayoutInflater#inflate(int, ViewGroup, boolean)}
+ * to specify a root view and to prevent attachment to the root.
+ *
+ * @param position The position of the item within the adapter's data set of the item whose
+ * view we want.
+ * @param convertView The old view to reuse, if possible. Note: You should check that this view
+ * is non-null and of an appropriate type before using. If it is not possible
+ * to convert this view to display the correct data, this method can create a
+ * new view. Heterogeneous lists can specify their number of view types, so
+ * that this View is always of the right type
+ * (see {@link #getViewTypeCount()} and {@link #getItemViewType(int)}).
+ * @param parent The parent that this view will eventually be attached to
+ * @return A View corresponding to the data at the specified position.
+ */
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ CallMethodInfo callMethodInfo = getItem(position);
+ boolean volteInUse = (callMethodInfo.mSubId > 0) &&
+ VolteUtils.isVolteInUse(mContext, callMethodInfo.mSubId);
+
+ SpinnerItemViewHolder holder = null;
+ // Note: if mVolteInUse changes, it invalidates cached views
+ if (convertView == null || (holder = (SpinnerItemViewHolder)convertView.getTag()) == null
+ || holder.volteInUse != volteInUse)
+ {
+ convertView = LayoutInflater.from(mContext).inflate(R.layout
+ .call_method_spinner_item, parent, false);
+ if (holder == null) holder = new SpinnerItemViewHolder();
+ convertView.setTag(holder);
+ holder.volteInUse = volteInUse;
+ holder.callTypeIcon = (ImageView) convertView.findViewById(R.id
+ .call_method_spinner_item_image);
+ holder.volteIcon = (ImageView) convertView.findViewById(R.id
+ .call_method_spinner_volte_image);
+ }
+ if (mShowVolte && volteInUse) {
+ holder.volteIcon.setVisibility(View.VISIBLE);
+ } else {
+ holder.volteIcon.setVisibility(View.GONE);
+ }
+ setIcon(convertView, callMethodInfo);
+
+ return convertView;
+ }
+
+ /**
+ * <p>Get a {@link View} that displays in the drop down popup
+ * the data at the specified position in the data set.</p>
+ *
+ * @param position index of the item whose view we want.
+ * @param convertView the old view to reuse, if possible. Note: You should
+ * check that this view is non-null and of an appropriate type before
+ * using. If it is not possible to convert this view to display the
+ * correct data, this method can create a new view.
+ * @param parent the parent that this view will eventually be attached to
+ * @return a {@link View} corresponding to the data at the
+ * specified position.
+ */
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ CallMethodInfo callMethodInfo = getItem(position);
+ boolean volteInUse = (callMethodInfo.mSubId > 0) &&
+ VolteUtils.isVolteInUse(mContext, callMethodInfo.mSubId);
+
+ SpinnerDropDownItemViewHolder holder = null;
+ int tag = volteInUse ? R.string.call_method_spinner_volte_drop_down_tag :
+ R.string.call_method_spinner_drop_down_tag;
+ if (convertView == null || (holder = (SpinnerDropDownItemViewHolder) convertView.getTag
+ (tag)) == null) {
+ int resId = R.layout.call_method_spinner_dropdown_item;
+ convertView = LayoutInflater.from(mContext).inflate(resId, parent, false);
+ holder = new SpinnerDropDownItemViewHolder();
+ holder.volteInUse = volteInUse;
+ holder.callTypeIcon = (ImageView) convertView.findViewById(R.id
+ .call_method_spinner_item_image);
+ holder.volteIcon = (ImageView) convertView.findViewById(R.id
+ .call_method_spinner_volte_image);
+ holder.textView = (TextView) convertView.findViewById(R.id
+ .call_method_spinner_item_text);
+ convertView.setTag(tag, holder);
+ }
+ if (mShowVolte && volteInUse) {
+ holder.volteIcon.setVisibility(View.VISIBLE);
+ } else {
+ holder.volteIcon.setVisibility(View.GONE);
+ }
+ setIcon(convertView, callMethodInfo);
+ holder.textView.setText(callMethodInfo.mName);
+
+ return convertView;
+ }
+
+ private void setIcon(View convertView, CallMethodInfo callMethodInfo) {
+ ImageView icon = (ImageView) convertView.findViewById(R.id.call_method_spinner_item_image);
+ if (callMethodInfo.mBrandIcon != null) {
+ icon.setImageDrawable(callMethodInfo.mBrandIcon);
+ icon.getDrawable().setTintList(null);
+ icon.setBackground(null);
+ } else {
+ int drawableId = getIconForSlot(callMethodInfo.mSlotId);
+
+ Drawable forground = mContext.getDrawable(drawableId);
+ Drawable background = mContext.getDrawable(R.drawable.ic_sim_backing);
+
+ if (callMethodInfo.mColor != NO_COLOR) {
+ forground.setTint(callMethodInfo.mColor);
+ } else {
+ forground.setTint(mContext.getResources().getColor(R.color.sim_icon_color));
+ }
+
+ Drawable[] layers = {background, forground};
+ LayerDrawable layerDrawable = new LayerDrawable(layers);
+ icon.setImageDrawable(layerDrawable);
+ }
+ }
+
+ public static int getIconForSlot(int slotId) {
+ switch (slotId) {
+ case -1:
+ // SubscriptionManager.INVALID_SIM_SLOT_INDEX
+ return R.drawable.ic_nosim;
+ case 1:
+ return R.drawable.ic_sim_2;
+ case 2:
+ return R.drawable.ic_sim_3;
+ case 3:
+ return R.drawable.ic_sim_4;
+ default:
+ return R.drawable.ic_sim_1;
+ }
+ }
+
+ /**
+ * Map call method component names to spinner positions.
+ * @param callMethodInfo new call method
+ */
+ private void processCallMethod(CallMethodInfo callMethodInfo) {
+ if (mComponentMap == null) {
+ mComponentMap = new HashMap<String, Integer>();
+ }
+ if (callMethodInfo != null) {
+ String key = getCallMethodKey(callMethodInfo);
+ mComponentMap.put(key, mComponentMap.size());
+ }
+ }
+
+ /**
+ * Map call method component names to spinner positions.
+ * @param callMethodInfoList list of current call methods
+ */
+ private void processCallMethods(Collection<CallMethodInfo> callMethodInfoList) {
+ if (mComponentMap == null) {
+ mComponentMap = new HashMap<String, Integer>();
+ }
+ if (callMethodInfoList != null) {
+ for (CallMethodInfo info : callMethodInfoList) {
+ processCallMethod(info);
+ }
+ }
+ }
+
+ /**
+ * Returns the position of the specified component within the spinner
+ * @param key String key for call method lookup
+ * @return Position of specified component, POSITION_UNKNOWN if not found.
+ */
+ public int getPosition(String key) {
+ int position = POSITION_UNKNOWN;
+ if (!TextUtils.isEmpty(key)) {
+ position = mComponentMap.containsKey(key) ?
+ mComponentMap.get(key) : POSITION_UNKNOWN;
+ }
+ return position;
+ }
+
+ /**
+ * Returns an ID for the specified CallMethodInfo
+ * @param info CallMethodInfo to create id for
+ * @return String key for specified CallMethodInfo.
+ */
+ public static String getCallMethodKey(CallMethodInfo info) {
+ if (info == null) {
+ return null;
+ }
+ return String.valueOf(info.hashCode());
+ }
+
+ private static class SpinnerItemViewHolder {
+ ImageView callTypeIcon;
+ ImageView volteIcon;
+ boolean volteInUse;
+ }
+
+ private static class SpinnerDropDownItemViewHolder {
+ ImageView callTypeIcon;
+ ImageView volteIcon;
+ TextView textView;
+ boolean volteInUse;
+ }
+}
diff --git a/src-ambient/com/android/phone/common/incall/ContactsDataSubscription.java b/src-ambient/com/android/phone/common/incall/ContactsDataSubscription.java
new file mode 100644
index 0000000..2b38b1e
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/ContactsDataSubscription.java
@@ -0,0 +1,123 @@
+/*
+ * 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.phone.common.incall;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.phone.common.ambient.SingletonHolder;
+import com.android.phone.common.ambient.TypedPendingResult;
+import com.android.phone.common.incall.api.InCallListeners;
+import com.android.phone.common.incall.api.InCallQueries;
+import com.android.phone.common.nudge.api.NudgeQueries;
+import com.cyanogen.ambient.discovery.util.NudgeKey;
+import com.cyanogen.ambient.incall.extension.InCallContactInfo;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ContactsDataSubscription extends DialerDataSubscription {
+
+ public ContactsDataSubscription(Context context) {
+ super(context);
+ }
+
+ private static final SingletonHolder<ContactsDataSubscription, Context> sInstance =
+ new SingletonHolder<ContactsDataSubscription, Context>() {
+
+ @Override
+ protected ContactsDataSubscription create(Context context) {
+ // Let's get started here.
+ return new ContactsDataSubscription(context);
+ }
+ };
+
+
+ public static ContactsDataSubscription get(Context context) {
+ return sInstance.get(context);
+ }
+
+ public static boolean isCreated() {
+ return sInstance.isCreated();
+ }
+
+ public static void init(Context context) {
+ ContactsDataSubscription.get(context).refresh();
+ }
+
+ @Override
+ public void onDynamicRefreshRequested(ArrayList<TypedPendingResult> queries,
+ ComponentName componentName) {
+ queries.add(InCallQueries.getCallMethodAuthenticated(mClient, componentName));
+ queries.add(InCallQueries.getCallMethodAccountHandle(mClient, componentName));
+ }
+
+ @Override
+ protected void enableListeners(CallMethodInfo mod) {
+ InCallListeners.enableAuthListener(this, mod);
+ }
+
+ @Override
+ protected void disableListeners(CallMethodInfo mod) {
+ InCallListeners.disableAuthListener(this, mod);
+ }
+
+ @Override
+ protected void requestedModInfo(ArrayList<TypedPendingResult> queries,
+ ComponentName componentName) {
+
+ queries.add(InCallQueries.getCallMethodInfo(mClient, componentName));
+ queries.add(InCallQueries.getCallMethodStatus(mClient, componentName));
+ queries.add(InCallQueries.getCallMethodMimeType(mClient, componentName));
+ queries.add(InCallQueries.getCallMethodVideoCallableMimeType(mClient, componentName));
+ queries.add(InCallQueries.getCallMethodAuthenticated(mClient, componentName));
+ queries.add(InCallQueries.getLoginIntent(mClient, componentName));
+ queries.add(InCallQueries.getDefaultDirectorySearchIntent(mClient, componentName));
+ queries.add(InCallQueries.getCallMethodImMimeType(mClient, componentName));
+
+ TypedPendingResult fragLogin = NudgeQueries.getNudgeConfig(mClient, mContext, componentName,
+ NudgeKey.INCALL_CONTACT_FRAGMENT_LOGIN);
+ if (fragLogin != null) {
+ queries.add(fragLogin);
+ }
+ TypedPendingResult cardLogin = NudgeQueries.getNudgeConfig(mClient, mContext, componentName,
+ NudgeKey.INCALL_CONTACT_CARD_LOGIN);
+ if (cardLogin != null) {
+ queries.add(cardLogin);
+ }
+ TypedPendingResult cardDownload = NudgeQueries.getNudgeConfig(mClient, mContext,
+ componentName, NudgeKey.INCALL_CONTACT_CARD_DOWNLOAD);
+ if (cardDownload != null) {
+ queries.add(cardDownload);
+ }
+ }
+
+ public static void refreshPendingIntents(InCallContactInfo contactInfo) {
+ // TODO: implement
+ }
+
+ public Set<String> getAllPluginComponentNames() {
+ Set<String> names = new HashSet<String>();
+ HashMap<ComponentName, CallMethodInfo> plugins = this.getPluginInfo();
+ for (ComponentName cn : plugins.keySet()) {
+ names.add(cn.flattenToString());
+ }
+ return names;
+ }
+}
diff --git a/src-ambient/com/android/phone/common/incall/CreditBarHelper.java b/src-ambient/com/android/phone/common/incall/CreditBarHelper.java
new file mode 100644
index 0000000..7ef7584
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/CreditBarHelper.java
@@ -0,0 +1,105 @@
+package com.android.phone.common.incall;
+
+import android.app.PendingIntent;
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.phone.common.R;
+
+/**
+ * Helper method used to handle incall credit bars
+ *
+ * The credit bar's origin code comes from google. We've added a second credit bar in dialer that
+ * uses these same helper method.
+ */
+public class CreditBarHelper {
+
+ private static final String TAG = CreditBarHelper.class.getSimpleName();
+
+ public interface CreditBarVisibilityListener {
+ public void creditsBarVisibilityChanged(int visibility);
+ }
+
+ public static void clearCallRateInformation(ViewGroup v, CreditBarVisibilityListener cpvl) {
+ setCallRateInformation(null, v, null, null, null, false, cpvl);
+ }
+
+ public static void setCallRateInformation(Resources res, ViewGroup creditsBar,
+ String creditText, String buttonText, final PendingIntent buttonIntent,
+ boolean warnIfLow, CreditBarVisibilityListener cpvl) {
+ if (TextUtils.isEmpty(creditText) && TextUtils.isEmpty(buttonText) &&
+ buttonIntent == null) {
+ if (creditsBar.getVisibility() != View.GONE) {
+ creditsBar.setVisibility(View.GONE);
+ cpvl.creditsBarVisibilityChanged(View.GONE);
+ }
+ return;
+ }
+ if (creditsBar.getVisibility() != View.VISIBLE) {
+ creditsBar.setVisibility(View.VISIBLE);
+ cpvl.creditsBarVisibilityChanged(View.VISIBLE);
+ }
+
+ // These views already exist, we are hijacking them.
+ TextView credit = (TextView) creditsBar.findViewById(R.id.ild_country);
+ TextView button = (TextView) creditsBar.findViewById(R.id.ild_rate);
+
+ if (res != null) {
+ int color = warnIfLow ?
+ res.getColor(R.color.credit_banner_alert_color) :
+ res.getColor(R.color.credit_banner_text);
+ credit.setTextColor(color);
+ }
+
+ credit.setText(creditText);
+ button.setText(buttonText);
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ try {
+ if (buttonIntent != null) {
+ buttonIntent.send();
+ } else {
+ Log.wtf(TAG, "The intent we attempted to fire was null");
+ }
+ } catch (PendingIntent.CanceledException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ public static void callMethodCredits(ViewGroup v, CallMethodInfo cmi, Resources r,
+ CreditBarVisibilityListener cpvl) {
+ String creditText = cmi.getCreditsDescriptionText(r);
+ String buttonText = null;
+ PendingIntent button;
+
+ boolean warnIfLow = false;
+ if (TextUtils.isEmpty(creditText)) {
+ clearCallRateInformation(v, cpvl);
+ return;
+ } else {
+ if (cmi.mIsAuthenticated) {
+ button = cmi.mManageCreditIntent;
+ if (cmi.showSubscriptions()) {
+ buttonText = cmi.mSubscriptionButtonText;
+ } else {
+ if (cmi.getCurrencyAmount() <= cmi.mCreditWarn) {
+ warnIfLow = true;
+ }
+ buttonText = cmi.mCreditButtonText;
+ }
+ } else {
+ buttonText = r.getString(R.string.sign_in_credit_banner_text);
+ creditText = "";
+ button = cmi.mLoginIntent;
+ }
+ }
+ setCallRateInformation(r, v, creditText, buttonText, button, warnIfLow, cpvl);
+ }
+}
diff --git a/src-ambient/com/android/phone/common/incall/DialerDataSubscription.java b/src-ambient/com/android/phone/common/incall/DialerDataSubscription.java
new file mode 100644
index 0000000..d4b6071
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/DialerDataSubscription.java
@@ -0,0 +1,193 @@
+/*
+ * 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.util.Log;
+
+import com.android.phone.common.ambient.SingletonHolder;
+import com.android.phone.common.ambient.TypedPendingResult;
+import com.android.phone.common.incall.api.InCallListeners;
+import com.android.phone.common.incall.api.InCallQueries;
+import com.android.phone.common.incall.api.InCallResults;
+import com.android.phone.common.nudge.api.NudgeQueries;
+import com.cyanogen.ambient.common.api.Result;
+import com.cyanogen.ambient.discovery.results.BundleResult;
+import com.cyanogen.ambient.discovery.util.NudgeKey;
+import com.cyanogen.ambient.incall.extension.GetCreditInfoResult;
+import com.cyanogen.ambient.incall.results.AccountHandleResult;
+import com.cyanogen.ambient.incall.results.AuthenticationStateResult;
+import com.cyanogen.ambient.incall.results.GetCreditInfoResultResult;
+import com.cyanogen.ambient.incall.results.HintTextResultResult;
+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.android.phone.common.ambient.AmbientDataSubscription;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.cyanogen.ambient.incall.util.InCallHelper.NO_COLOR;
+
+/**
+ * Call Method Helper - In charge of loading InCall Mod Data
+ *
+ * Fragments and Activities can subscribe to changes with subscribe.
+ */
+public class DialerDataSubscription extends AmbientDataSubscription<CallMethodInfo> {
+
+ protected static final String TAG = DialerDataSubscription.class.getSimpleName();
+
+ private static final int CALL_METHOD_TYPE = -1;
+
+ public DialerDataSubscription(Context context) {
+ super(context);
+ }
+
+ public static final SingletonHolder<DialerDataSubscription, Context> sInstance =
+ new SingletonHolder<DialerDataSubscription, Context>() {
+
+ @Override
+ protected DialerDataSubscription create(Context context) {
+ return new DialerDataSubscription(context);
+ }
+
+ };
+
+ public static DialerDataSubscription get(Context context) {
+ return sInstance.get(context);
+ }
+
+ public static boolean isCreated() {
+ return sInstance.isCreated();
+ }
+
+ public static void init(Context context) {
+ sInstance.get(context).refresh();
+ }
+
+ @Override
+ protected void onRefreshRequested() {
+ InCallQueries.updateCallPlugins(this);
+ }
+
+ @Override
+ protected List<ComponentName> getPluginComponents(Result result) {
+ return InCallResults.gotInstalledPlugins((InstalledPluginsResult)result);
+ }
+
+ @Override
+ protected void requestedModInfo(ArrayList<TypedPendingResult> queries,
+ ComponentName componentName) {
+
+ queries.add(InCallQueries.getCallMethodInfo(mClient, componentName));
+ queries.add(InCallQueries.getCallMethodStatus(mClient, componentName));
+ queries.add(InCallQueries.getCallMethodMimeType(mClient, componentName));
+ queries.add(InCallQueries.getCallMethodVideoCallableMimeType(mClient, componentName));
+ queries.add(InCallQueries.getCallMethodAuthenticated(mClient, componentName));
+ queries.add(InCallQueries.getLoginIntent(mClient, componentName));
+ queries.add(InCallQueries.getSettingsIntent(mClient, componentName));
+ queries.add(InCallQueries.getCreditInfo(mClient, componentName));
+ queries.add(InCallQueries.getHintText(mClient, componentName));
+ queries.add(InCallQueries.getManageCreditsIntent(mClient, componentName));
+
+ TypedPendingResult creditQuery = NudgeQueries.getNudgeConfig(mClient, mContext,
+ componentName, NudgeKey.INCALL_CREDIT_NUDGE);
+ if (creditQuery != null) {
+ queries.add(creditQuery);
+ }
+ }
+
+ @Override
+ protected CallMethodInfo getNewModObject(ComponentName componentName) {
+ CallMethodInfo callMethodInfo = new CallMethodInfo();
+ callMethodInfo.mComponent = componentName;
+ callMethodInfo.mSlotId = CALL_METHOD_TYPE;
+ callMethodInfo.mSubId = CALL_METHOD_TYPE;
+ callMethodInfo.mColor = NO_COLOR;
+ callMethodInfo.mIsInCallProvider = true;
+ return callMethodInfo;
+ }
+
+ @Override
+ protected void onDynamicRefreshRequested(ArrayList<TypedPendingResult> queries,
+ ComponentName componentName) {
+
+ queries.add(InCallQueries.getCallMethodAuthenticated(mClient, componentName));
+ queries.add(InCallQueries.getCreditInfo(mClient, componentName));
+ }
+
+ @Override
+ protected void enableListeners(CallMethodInfo cn) {
+ InCallListeners.enableCreditListener(this, cn);
+ InCallListeners.enableAuthListener(this, cn);
+ }
+
+ @Override
+ protected void disableListeners(CallMethodInfo cn) {
+ InCallListeners.disableCreditListener(this, cn);
+ InCallListeners.disableAuthListener(this, cn);
+ }
+
+ @Override
+ protected void onPostResult(CallMethodInfo cmi, Result r, int type) {
+ switch (type) {
+ case TypedPendingResult.GENERAL_MIME_TYPE:
+ case TypedPendingResult.IM_MIME_TYPE:
+ case TypedPendingResult.VIDEO_MIME_TYPE:
+ InCallResults.gotMimeType(cmi, (MimeTypeResult)r, type);
+ break;
+ case TypedPendingResult.SETTINGS_INTENT:
+ case TypedPendingResult.CREDIT_INTENT:
+ case TypedPendingResult.LOGIN_INTENT:
+ case TypedPendingResult.DEFAULT_DIRECTORY_SEARCH_INTENT:
+ InCallResults.gotIntent(cmi, (PendingIntentResult)r, type);
+ break;
+ case TypedPendingResult.GENERAL_DATA:
+ InCallResults.gotGeneralInfo(cmi, this, (InCallProviderInfoResult)r);
+ break;
+ case TypedPendingResult.STATUS:
+ InCallResults.gotStatus(cmi, (PluginStatusResult)r);
+ break;
+ case TypedPendingResult.AUTHENTICATION:
+ InCallResults.gotAuthenticationState(cmi, (AuthenticationStateResult)r);
+ break;
+ case TypedPendingResult.CREDIT_INFO:
+ GetCreditInfoResult gcir = ((GetCreditInfoResultResult)r).result;
+ InCallResults.gotCreditData(cmi, gcir);
+ break;
+ case TypedPendingResult.ACCOUNT_HANDLE:
+ InCallResults.gotAccountHandle(cmi, mContext, (AccountHandleResult)r);
+ break;
+ case TypedPendingResult.HINT_TEXT:
+ InCallResults.gotHintText(cmi, (HintTextResultResult)r);
+ break;
+ case TypedPendingResult.INCALL_CONTACT_CARD_DOWNLOAD:
+ case TypedPendingResult.INCALL_CONTACT_CARD_LOGIN:
+ case TypedPendingResult.INCALL_CONTACT_FRAGMENT_LOGIN:
+ case TypedPendingResult.INCALL_CREDIT_NUDGE:
+ InCallResults.gotNudgeData(mContext, cmi, (BundleResult)r, type);
+ break;
+ default:
+ Log.e(TAG, "Unhandled result type!");
+ break;
+ }
+ }
+}
diff --git a/src-ambient/com/android/phone/common/incall/StartInCallCallReceiver.java b/src-ambient/com/android/phone/common/incall/StartInCallCallReceiver.java
new file mode 100644
index 0000000..52bfdd0
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/StartInCallCallReceiver.java
@@ -0,0 +1,62 @@
+/*
+ * 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.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+
+public class StartInCallCallReceiver extends ResultReceiver {
+ private static final String TAG = StartInCallCallReceiver.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private WeakReference<Receiver> mReceiver;
+
+ public interface InCallCallListener {
+ void onResult(int resultCode);
+ }
+
+ public StartInCallCallReceiver(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 {
+ 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-ambient/com/android/phone/common/incall/api/ApiHelper.java b/src-ambient/com/android/phone/common/incall/api/ApiHelper.java
new file mode 100644
index 0000000..8a20aa2
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/api/ApiHelper.java
@@ -0,0 +1,38 @@
+/*
+ * 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.phone.common.incall.api;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.phone.common.ambient.AmbientDataSubscription;
+import com.android.phone.common.incall.DialerDataSubscription;
+import com.android.phone.common.incall.CallMethodInfo;
+import com.android.phone.common.incall.ContactsDataSubscription;
+import com.cyanogen.ambient.incall.InCallApi;
+import com.cyanogen.ambient.incall.InCallServices;
+
+/**
+ * Helper for getting instance and InCall Api
+ */
+public class ApiHelper {
+
+ static InCallApi thisApi() {
+ return InCallServices.getInstance();
+ }
+
+}
diff --git a/src-ambient/com/android/phone/common/incall/api/InCallListeners.java b/src-ambient/com/android/phone/common/incall/api/InCallListeners.java
new file mode 100644
index 0000000..a25c1e1
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/api/InCallListeners.java
@@ -0,0 +1,84 @@
+/*
+ * 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.phone.common.incall.api;
+
+import android.content.Context;
+
+import com.android.phone.common.ambient.AmbientDataSubscription;
+import com.android.phone.common.incall.listeners.AuthenticationListenerImpl;
+import com.android.phone.common.incall.listeners.CallCreditListenerImpl;
+import com.android.phone.common.incall.CallMethodInfo;
+
+/**
+ * Data Listeners that incall implements.
+ */
+public class InCallListeners extends ApiHelper {
+
+ /**
+ * Enables credit listener on a Mod.
+ * @param instance us
+ * @param callMethodInfo CallMethod to add listener to
+ */
+ public static void enableCreditListener(AmbientDataSubscription<CallMethodInfo> instance,
+ CallMethodInfo callMethodInfo) {
+ CallCreditListenerImpl listener = CallCreditListenerImpl.getInstance(instance.mContext,
+ callMethodInfo.mComponent);
+ thisApi().addCreditListener(instance.mClient, callMethodInfo.mComponent,
+ listener);
+ callMethodInfo.setCreditListener(listener);
+ }
+
+ /**
+ * Disables credit listener on a Mod.
+ * @param instance us
+ * @param callMethodInfo CallMethod to remove listener from
+ */
+ public static void disableCreditListener(AmbientDataSubscription<CallMethodInfo> instance,
+ CallMethodInfo callMethodInfo) {
+ CallCreditListenerImpl listener = callMethodInfo.getCreditListener();
+ thisApi().removeCreditListener(instance.mClient, callMethodInfo.mComponent,
+ listener);
+ callMethodInfo.setCreditListener(null);
+ }
+
+ /**
+ * Enable Auth Listener on a Mod
+ * @param instance us
+ * @param callMethodInfo CallMethod to add listener to
+ */
+ public static void enableAuthListener(AmbientDataSubscription<CallMethodInfo> instance,
+ CallMethodInfo callMethodInfo) {
+ AuthenticationListenerImpl listener = AuthenticationListenerImpl.getInstance(
+ instance.mContext, callMethodInfo.mComponent);
+ thisApi().addAuthenticationListener(instance.mClient, callMethodInfo.mComponent,
+ listener);
+ callMethodInfo.setAuthListener(listener);
+ }
+
+ /**
+ * Disable Auth Listener on a Mod
+ * @param instance us
+ * @param callMethodInfo CallMethod to remove listener from
+ */
+ public static void disableAuthListener(AmbientDataSubscription<CallMethodInfo> instance,
+ CallMethodInfo callMethodInfo) {
+ AuthenticationListenerImpl listener = callMethodInfo.getAuthListener();
+ thisApi().removeAuthenticationListener(instance.mClient,
+ callMethodInfo.mComponent, listener);
+ callMethodInfo.setAuthListener(null);
+ }
+}
diff --git a/src-ambient/com/android/phone/common/incall/api/InCallQueries.java b/src-ambient/com/android/phone/common/incall/api/InCallQueries.java
new file mode 100644
index 0000000..384cfdb
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/api/InCallQueries.java
@@ -0,0 +1,230 @@
+/*
+ * 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.phone.common.incall.api;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.phone.common.ambient.AmbientDataSubscription;
+import com.android.phone.common.ambient.TypedPendingResult;
+import com.android.phone.common.incall.CallMethodInfo;
+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;
+
+/**
+ * Queries for incall plugins
+ */
+public class InCallQueries extends ApiHelper {
+
+ private static final String TAG = InCallQueries.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ /**
+ * Prepare to query and fire off ModCore calls in all directions
+ */
+ public static void updateCallPlugins(AmbientDataSubscription<CallMethodInfo> instance) {
+ thisApi().getInstalledPlugins(instance.mClient).setResultCallback(instance.BOOTSTRAP);
+ }
+
+ /**
+ * Get the plugin info
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getCallMethodInfo(AmbientApiClient client,
+ ComponentName componentName) {
+
+ return new TypedPendingResult(thisApi().getProviderInfo(client, componentName),
+ TypedPendingResult.GENERAL_DATA);
+ }
+
+ /**
+ * Get our mod enabled status
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getCallMethodStatus(AmbientApiClient client,
+ ComponentName componentName) {
+ return new TypedPendingResult(thisApi().getPluginStatus(client, componentName),
+ TypedPendingResult.STATUS);
+ }
+
+ /**
+ * Get the mod mimetype
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getCallMethodMimeType(AmbientApiClient client,
+ ComponentName componentName) {
+ return new TypedPendingResult(thisApi().getCallableMimeType(client, componentName),
+ TypedPendingResult.GENERAL_MIME_TYPE);
+ }
+
+ /**
+ * Get the plugin video mimetype
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getCallMethodVideoCallableMimeType(AmbientApiClient client,
+ ComponentName componentName) {
+ return new TypedPendingResult(thisApi().getVideoCallableMimeType(client, componentName),
+ TypedPendingResult.VIDEO_MIME_TYPE);
+ }
+
+ /**
+ * Get the plugin im mime type
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getCallMethodImMimeType(AmbientApiClient client,
+ ComponentName componentName) {
+ return new TypedPendingResult(thisApi().getImMimeType(client, componentName),
+ TypedPendingResult.IM_MIME_TYPE);
+ }
+
+ /**
+ * Get the Authentication state of the callmethod
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getCallMethodAuthenticated(AmbientApiClient client,
+ ComponentName componentName) {
+ return new TypedPendingResult(thisApi().getAuthenticationState(client, componentName),
+ TypedPendingResult.AUTHENTICATION);
+ }
+
+ /**
+ * Get the credits intent of the mod
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getManageCreditsIntent(AmbientApiClient client,
+ ComponentName componentName) {
+ return new TypedPendingResult(thisApi().getManageCreditsIntent(client, componentName),
+ TypedPendingResult.CREDIT_INTENT);
+ }
+
+ /**
+ * Get the login intent of the mod
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getLoginIntent(AmbientApiClient client,
+ ComponentName componentName) {
+ return new TypedPendingResult(thisApi().getLoginIntent(client, componentName),
+ TypedPendingResult.LOGIN_INTENT);
+ }
+
+ /**
+ * Get the settings intent for the callmethod
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getSettingsIntent(AmbientApiClient client,
+ ComponentName componentName) {
+ return new TypedPendingResult(thisApi().getSettingsIntent(client, componentName),
+ TypedPendingResult.SETTINGS_INTENT);
+ }
+
+ /**
+ * Get the credit info of the mod
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getCreditInfo(AmbientApiClient client,
+ ComponentName componentName) {
+ return new TypedPendingResult(thisApi().getCreditInfo(client, componentName),
+ TypedPendingResult.CREDIT_INFO);
+ }
+
+ /**
+ * Get the account handle of the mod
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getCallMethodAccountHandle(AmbientApiClient client,
+ ComponentName componentName) {
+ return new TypedPendingResult(thisApi().getAccountHandle(client, componentName),
+ TypedPendingResult.ACCOUNT_HANDLE);
+ }
+
+ /**
+ * Get the hint texts for the callmethod
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getHintText(AmbientApiClient client,
+ ComponentName componentName) {
+ return new TypedPendingResult(thisApi().getHintText(client, componentName),
+ TypedPendingResult.HINT_TEXT);
+ }
+
+ /**
+ * Get the default search intent for the mod
+ * @param componentName of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getDefaultDirectorySearchIntent(AmbientApiClient client,
+ ComponentName componentName) {
+ return new TypedPendingResult(thisApi().getDirectorySearchIntent(client, componentName,
+ Uri.parse("")), TypedPendingResult.DEFAULT_DIRECTORY_SEARCH_INTENT);
+ }
+
+ /**
+ * Send analytics to our mod
+ * @param client ambient client
+ * @param componentName Componentname of the mod to send to
+ * @param event the event to send.
+ */
+ public static void shipAnalyticsToPlugin(AmbientApiClient client, ComponentName componentName,
+ Event event) {
+ if (componentName == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "componentName: " + componentName.toShortString()
+ + ":Event: " + event.toString());
+ }
+ thisApi().sendAnalyticsEventToPlugin(client, componentName, event)
+ .setResultCallback(new ResultCallback<Result>() {
+ @Override
+ public void onResult(Result result) {
+ if (DEBUG) {
+ Log.v(TAG, "Event sent with result: " + result.getStatus()
+ .getStatusMessage());
+ }
+ }
+ });
+ }
+}
diff --git a/src-ambient/com/android/phone/common/incall/api/InCallResults.java b/src-ambient/com/android/phone/common/incall/api/InCallResults.java
new file mode 100644
index 0000000..b636f56
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/api/InCallResults.java
@@ -0,0 +1,283 @@
+/*
+ * 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.phone.common.incall.api;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.phone.common.ambient.AmbientDataSubscription;
+import com.android.phone.common.ambient.TypedPendingResult;
+import com.android.phone.common.incall.CallMethodInfo;
+import com.android.phone.common.incall.utils.CallMethodUtils;
+import com.android.phone.common.ambient.utils.PluginUtils;
+import com.android.phone.common.nudge.api.NudgeTypedResult;
+import com.cyanogen.ambient.discovery.results.BundleResult;
+import com.cyanogen.ambient.discovery.util.NudgeKey;
+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.HintTextResult;
+import com.cyanogen.ambient.incall.extension.StatusCodes;
+import com.cyanogen.ambient.incall.results.AccountHandleResult;
+import com.cyanogen.ambient.incall.results.AuthenticationStateResult;
+import com.cyanogen.ambient.incall.results.HintTextResultResult;
+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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class that handles writing results to our CallMethodInfo object
+ */
+public class InCallResults extends ApiHelper {
+
+ private static final String TAG = InCallResults.class.getSimpleName();
+
+ public static List<ComponentName> gotInstalledPlugins(InstalledPluginsResult result) {
+ if (result.components == null) {
+ return new ArrayList<ComponentName>();
+ }
+ return result.components;
+ }
+
+ public static void gotStatus(CallMethodInfo callMethodInfo, PluginStatusResult result) {
+ callMethodInfo.mStatus = result.status;
+ }
+
+ public static void gotMimeType(CallMethodInfo callMethodInfo, MimeTypeResult result, int type) {
+ switch(type) {
+ case TypedPendingResult.GENERAL_MIME_TYPE:
+ callMethodInfo.mMimeType = result.mimeType;
+ break;
+ case TypedPendingResult.VIDEO_MIME_TYPE:
+ callMethodInfo.mVideoCallableMimeType = result.mimeType;
+ break;
+ case TypedPendingResult.IM_MIME_TYPE:
+ callMethodInfo.mImMimeType = result.mimeType;
+ break;
+ }
+ }
+
+ public static void gotIntent(CallMethodInfo callMethodInfo, PendingIntentResult result,
+ int type) {
+ switch(type) {
+ case TypedPendingResult.LOGIN_INTENT:
+ callMethodInfo.mLoginIntent = result.intent;
+ break;
+ case TypedPendingResult.CREDIT_INTENT:
+ callMethodInfo.mManageCreditIntent = result.intent;
+ break;
+ case TypedPendingResult.SETTINGS_INTENT:
+ callMethodInfo.mSettingsIntent = result.intent;
+ break;
+ case TypedPendingResult.DEFAULT_DIRECTORY_SEARCH_INTENT:
+ callMethodInfo.mDefaultDirectorySearchIntent = result.intent;
+ break;
+ }
+ }
+
+ public static void gotAuthenticationState(CallMethodInfo callMethodInfo,
+ AuthenticationStateResult result) {
+ callMethodInfo.mIsAuthenticated
+ = result.result == StatusCodes.AuthenticationState.LOGGED_IN;
+ }
+
+ public static void gotAccountHandle(CallMethodInfo callMethodInfo, Context context,
+ AccountHandleResult result) {
+ gotAccountHandle(callMethodInfo, context, result.accountHandle);
+ }
+
+ private static void gotAccountHandle(CallMethodInfo callMethodInfo, Context context,
+ String accountHandle) {
+ callMethodInfo.mAccountHandle = accountHandle;
+ if (TextUtils.isEmpty(callMethodInfo.mAccountHandle)) {
+ callMethodInfo.mAccountHandle
+ = CallMethodUtils.lookupAccountHandle(context, callMethodInfo.mAccountType);
+ }
+ }
+
+ public static void gotGeneralInfo(CallMethodInfo callMethodInfo,
+ AmbientDataSubscription<CallMethodInfo> instance, InCallProviderInfoResult result) {
+
+ ComponentName componentName = callMethodInfo.mComponent;
+ InCallProviderInfo icpi = result.inCallProviderInfo;
+ if (icpi == null) {
+ instance.getPluginInfo().remove(componentName);
+ return;
+ }
+
+ Resources resources = PluginUtils.getPluginResources(instance.mContext, componentName);
+ if (resources == null) {
+ return;
+ }
+
+ callMethodInfo.mBrandIconId = icpi.getBrandIcon();
+ callMethodInfo.mLoginIconId = icpi.getLoginIcon();
+
+ try {
+ callMethodInfo.mSingleColorBrandIcon
+ = resources.getDrawable(icpi.getSingleColorBrandIcon());
+ callMethodInfo.mBrandIcon = resources.getDrawable(callMethodInfo.mBrandIconId);
+ callMethodInfo.mLoginIcon = resources.getDrawable(callMethodInfo.mLoginIconId);
+ callMethodInfo.mVoiceIcon = resources.getDrawable(icpi.getVoiceMimeIcon());
+ callMethodInfo.mImIcon = resources.getDrawable(icpi.getImMimeIcon());
+ callMethodInfo.mBadgeIcon = resources.getDrawable(icpi.getBadgeIcon());
+ callMethodInfo.mVideoIcon = resources.getDrawable(icpi.getVideoMimeIcon());
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Resource Not found: " + componentName.flattenToShortString());
+ instance.getPluginInfo().remove(componentName);
+ return;
+ }
+
+ callMethodInfo.mSubscriptionButtonText = icpi.getSubscriptionButtonText();
+ callMethodInfo.mCreditButtonText = icpi.getCreditsButtonText();
+ // If present, use the deprecated attribute defined hint text.
+ // These values may be overwritten by getHintText.
+ callMethodInfo.mT9HintDescriptionNoCreditOrSub = icpi.getT9HintDescription();
+ callMethodInfo.mT9HintDescriptionHasCreditOrSub = icpi.getT9HintDescription();
+ callMethodInfo.mDependentPackage = icpi.getDependentPackage();
+ callMethodInfo.mName = icpi.getTitle();
+ callMethodInfo.mSummary = icpi.getSummary();
+ callMethodInfo.mAccountType = icpi.getAccountType();
+ gotAccountHandle(callMethodInfo, instance.mContext, icpi.getAccountHandle());
+ }
+
+ public static void gotCreditData(CallMethodInfo callMethodInfo, GetCreditInfoResult result) {
+ if (result.creditInfo == null) {
+ // Build zero credit dummy if no result found.
+ callMethodInfo.mProviderCreditInfo = new CreditInfo(new CreditBalance(0, null), null);
+ } else {
+ callMethodInfo.mProviderCreditInfo = result.creditInfo;
+ }
+ }
+
+ public static void gotHintText(CallMethodInfo callMethodInfo,
+ HintTextResultResult resultResult) {
+ HintTextResult result = resultResult.result;
+ if (result != null) {
+ String hintText = result.getNoCreditOrSubscriptionHint();
+ if (!TextUtils.isEmpty(hintText)) {
+ callMethodInfo.mT9HintDescriptionNoCreditOrSub = hintText;
+ }
+ hintText = result.getHasCreditOrSubscriptionHint();
+ if (!TextUtils.isEmpty(hintText)) {
+ callMethodInfo.mT9HintDescriptionHasCreditOrSub = hintText;
+ }
+ }
+ }
+
+ public static void updateCreditInfo(AmbientDataSubscription<CallMethodInfo> instance,
+ ComponentName name, GetCreditInfoResult result) {
+ CallMethodInfo callMethodInfo = instance.getPluginIfExists(name);
+ if (callMethodInfo != null) {
+ gotCreditData(callMethodInfo, result);
+ instance.getPluginInfo().put(name, callMethodInfo);
+
+ // Since a CallMethodInfo object was updated here, we should let the subscribers know
+ instance.broadcast();
+ } else {
+ Log.wtf(TAG, "Call method does not exist, this should not happen");
+ }
+ }
+
+ public static void updateAuthenticationState(AmbientDataSubscription<CallMethodInfo> instance,
+ ComponentName name, int state) {
+ CallMethodInfo callMethodInfo = instance.getPluginIfExists(name);
+ if (callMethodInfo != null) {
+ callMethodInfo.mIsAuthenticated = state == StatusCodes.AuthenticationState.LOGGED_IN;
+ instance.getPluginInfo().put(name, callMethodInfo);
+
+ // Since a CallMethodInfo object was updated here, we should let the subscribers know
+ instance.broadcast();
+ } else {
+ Log.wtf(TAG, "Call method does not exist, this should not happen");
+ }
+ }
+
+ public static void gotNudgeData(Context context, CallMethodInfo callMethodInfo,
+ BundleResult result, int type) {
+
+ Bundle bundle = result.bundle;
+ if (bundle == null) {
+ return;
+ }
+ switch (type) {
+ case NudgeTypedResult.INCALL_CREDIT_NUDGE:
+ gotCreditWarn(callMethodInfo, bundle);
+ break;
+ case NudgeTypedResult.INCALL_CONTACT_FRAGMENT_LOGIN:
+ gotLoginSubtitle(callMethodInfo, bundle);
+ break;
+ case NudgeTypedResult.INCALL_CONTACT_CARD_LOGIN:
+ gotContactCardLogin(callMethodInfo, NudgeKey.INCALL_CONTACT_CARD_LOGIN, bundle,
+ context);
+ break;
+ case NudgeTypedResult.INCALL_CONTACT_CARD_DOWNLOAD:
+ gotContactCardDownload(callMethodInfo, NudgeKey.INCALL_CONTACT_CARD_DOWNLOAD,
+ bundle, context);
+ break;
+ }
+ }
+
+ private static void gotLoginSubtitle(CallMethodInfo callMethodInfo, Bundle bundle) {
+ callMethodInfo.mLoginSubtitle = bundle.getString(NudgeKey.NUDGE_PARAM_SUBTITLE);
+ }
+
+ private static void gotContactCardLogin(CallMethodInfo callMethodInfo, String key,
+ Bundle bundle, Context c) {
+ callMethodInfo.mLoginNudgeEnable = bundle.getBoolean(NudgeKey.NUDGE_PARAM_ENABLED, true)
+ && PreferenceManager.getDefaultSharedPreferences(c).getBoolean(
+ getKey(key, callMethodInfo), true);
+ callMethodInfo.mLoginNudgeSubtitle = bundle.getString(NudgeKey.NUDGE_PARAM_SUBTITLE);
+ callMethodInfo.mLoginNudgeActionText = bundle.getString(NudgeKey.NUDGE_PARAM_ACTION_TEXT);
+ }
+
+ private static void gotCreditWarn(CallMethodInfo callMethodInfo, Bundle bundle) {
+ Object creditWarn = bundle.get(NudgeKey.INCALL_PARAM_CREDIT_WARN);
+ if (creditWarn.getClass().equals(Integer.class)) {
+ callMethodInfo.mCreditWarn = (Integer) creditWarn;
+ } else if (creditWarn.getClass().equals(Float.class)) {
+ callMethodInfo.mCreditWarn = (Float) creditWarn;
+ } else {
+ Log.e(TAG, "Invalid value for Credit Warn limit: " + creditWarn);
+ }
+ }
+
+ private static void gotContactCardDownload(CallMethodInfo callMethodInfo, String key,
+ Bundle bundle, Context context) {
+ callMethodInfo.mInstallNudgeEnable = bundle.getBoolean(NudgeKey.NUDGE_PARAM_ENABLED, true)
+ && PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
+ getKey(key, callMethodInfo), true);
+ callMethodInfo.mInstallNudgeSubtitle = bundle.getString(NudgeKey.NUDGE_PARAM_SUBTITLE);
+ callMethodInfo.mInstallNudgeActionText = bundle.getString(NudgeKey.NUDGE_PARAM_ACTION_TEXT);
+ }
+
+
+ private static String getKey(String key, CallMethodInfo cmi) {
+ return cmi.mComponent.getClassName() + "." + key;
+ }
+}
diff --git a/src-ambient/com/android/phone/common/incall/listeners/AuthenticationListenerImpl.java b/src-ambient/com/android/phone/common/incall/listeners/AuthenticationListenerImpl.java
new file mode 100644
index 0000000..8bf3b53
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/listeners/AuthenticationListenerImpl.java
@@ -0,0 +1,58 @@
+/*
+ * 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.phone.common.incall.listeners;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.phone.common.incall.DialerDataSubscription;
+import com.android.phone.common.incall.api.InCallResults;
+import com.cyanogen.ambient.incall.extension.IAuthenticationListener;
+
+public class AuthenticationListenerImpl extends IAuthenticationListener.Stub {
+
+ private static final String TAG = AuthenticationListenerImpl.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private ComponentName mComponentName;
+ private Context mContext;
+
+ public static AuthenticationListenerImpl getInstance(Context context, ComponentName
+ componentName) {
+
+ return new AuthenticationListenerImpl(context, componentName);
+ }
+
+ private AuthenticationListenerImpl(Context context, ComponentName cn) {
+ mContext = context;
+ mComponentName = cn;
+ }
+
+ @Override
+ public void authenticationStateUpdated(int state) throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "Got authenticationStateUpdated for: " + mComponentName + " state: "
+ + state);
+ }
+
+ InCallResults.updateAuthenticationState(DialerDataSubscription.get(mContext),
+ mComponentName, state);
+ }
+
+} \ No newline at end of file
diff --git a/src-ambient/com/android/phone/common/incall/listeners/CallCreditListenerImpl.java b/src-ambient/com/android/phone/common/incall/listeners/CallCreditListenerImpl.java
new file mode 100644
index 0000000..9eef6cc
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/listeners/CallCreditListenerImpl.java
@@ -0,0 +1,53 @@
+/*
+ * 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.phone.common.incall.listeners;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.phone.common.incall.DialerDataSubscription;
+import com.android.phone.common.incall.api.InCallResults;
+import com.cyanogen.ambient.incall.extension.GetCreditInfoResult;
+import com.cyanogen.ambient.incall.extension.ICallCreditListener;
+
+public class CallCreditListenerImpl extends ICallCreditListener.Stub {
+
+ private static final String TAG = CallCreditListenerImpl.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private Context mContext;
+ private ComponentName mComponentName;
+
+ public static CallCreditListenerImpl getInstance(Context context, ComponentName componentName) {
+ return new CallCreditListenerImpl(context, componentName);
+ }
+
+ private CallCreditListenerImpl(Context context, ComponentName cn) {
+ mContext = context;
+ mComponentName = cn;
+ }
+
+ @Override
+ public void creditInfoUpdated(GetCreditInfoResult gcir) throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "got creditInfoUpdated for: " + mComponentName + " gcir: " + gcir);
+ }
+ InCallResults.updateCreditInfo(DialerDataSubscription.get(mContext), mComponentName, gcir);
+ }
+} \ No newline at end of file
diff --git a/src-ambient/com/android/phone/common/incall/utils/CallMethodFilters.java b/src-ambient/com/android/phone/common/incall/utils/CallMethodFilters.java
new file mode 100644
index 0000000..9a576b7
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/utils/CallMethodFilters.java
@@ -0,0 +1,94 @@
+/*
+ * 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.phone.common.incall.utils;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.android.phone.common.ambient.AmbientDataSubscription;
+import com.android.phone.common.incall.CallMethodInfo;
+import com.android.phone.common.incall.api.ApiHelper;
+import com.cyanogen.ambient.plugin.PluginStatus;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Helper for filtering call method objects in various ways.
+ */
+public class CallMethodFilters extends ApiHelper {
+
+ /**
+ * 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 == PluginStatus.ENABLED) {
+ output.put(key, value);
+ }
+ }
+ }
+
+ public static HashMap<ComponentName, CallMethodInfo> getAllEnabledCallMethods(
+ AmbientDataSubscription<CallMethodInfo> instance) {
+ HashMap<ComponentName, CallMethodInfo> cmi = new HashMap<>();
+ removeDisabled(instance.getPluginInfo(), cmi);
+ return cmi;
+ }
+
+ public static HashMap<ComponentName, CallMethodInfo> getAllEnabledAndHiddenCallMethods(
+ AmbientDataSubscription<CallMethodInfo> instance) {
+ HashMap<ComponentName, CallMethodInfo> cmi = new HashMap<>();
+ for (Map.Entry<ComponentName, CallMethodInfo> entry : instance.getPluginInfo().entrySet()) {
+ ComponentName key = entry.getKey();
+ CallMethodInfo value = entry.getValue();
+
+ if (value.mStatus == PluginStatus.ENABLED || value.mStatus == PluginStatus.HIDDEN) {
+ cmi.put(key, value);
+ }
+ }
+ return cmi;
+ }
+
+ public static CallMethodInfo getMethodForMimeType(String mimeType, boolean enableOnly,
+ AmbientDataSubscription<CallMethodInfo> instance) {
+
+ CallMethodInfo targetEntry = null;
+ for (CallMethodInfo entry : instance.getPluginInfo().values()) {
+ // During development, mimetype sometimes came back null
+ // find out why mimetype may be null
+ if (!TextUtils.isEmpty(entry.mMimeType)) {
+ if (enableOnly && entry.mStatus != PluginStatus.ENABLED) {
+ continue;
+ }
+ if (entry.mMimeType.equals(mimeType)) {
+ targetEntry = entry;
+ break;
+ }
+ }
+ }
+ return targetEntry;
+ }
+
+}
diff --git a/src-ambient/com/android/phone/common/incall/utils/CallMethodUtils.java b/src-ambient/com/android/phone/common/incall/utils/CallMethodUtils.java
new file mode 100644
index 0000000..d29f805
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/utils/CallMethodUtils.java
@@ -0,0 +1,220 @@
+/*
+ * 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.phone.common.incall.utils;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+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 android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import android.widget.Toast;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.phone.common.R;
+import com.android.phone.common.incall.CallMethodInfo;
+import com.android.phone.common.incall.StartInCallCallReceiver;
+import com.cyanogen.ambient.incall.extension.StatusCodes;
+
+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 {
+
+ private final static String TAG = CallMethodUtils.class.getSimpleName();
+ private final static boolean DEBUG = false;
+
+ 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";
+ public final static String PREF_WIFI_CALL = "pref_wifi_call";
+
+ public static CallMethodInfo getDefaultSimInfo(Context context) {
+ final TelecomManager telecomMgr =
+ (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+ PhoneAccountHandle handle =
+ telecomMgr.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
+ if (handle == null) {
+ return null;
+ }
+ return phoneToCallMethod(context, handle);
+ }
+
+ 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<>();
+ for (PhoneAccountHandle accountHandle : accountHandles) {
+ CallMethodInfo info = phoneToCallMethod(context, accountHandle);
+ if (info != null) {
+ callMethodInfoList.add(info);
+ }
+ }
+ return callMethodInfoList;
+ }
+
+ private static String getPhoneAccountName(Context context, PhoneAccount phoneAccount,
+ int slotId) {
+ if (phoneAccount == null) {
+ // Slot IDs are zero based
+ return context.getString(R.string.call_method_spinner_item_unknown_sim, slotId + 1);
+ } else if (phoneAccount.getLabel() != null) {
+ return phoneAccount.getLabel().toString();
+ }
+ return null;
+ }
+
+ private static int getPhoneAccountColor(SubscriptionInfo info) {
+ if (info != null) {
+ return info.getIconTint();
+ } else {
+ return NO_COLOR;
+ }
+ }
+
+ private static CallMethodInfo phoneToCallMethod(Context context,
+ PhoneAccountHandle phoneAccountHandle) {
+ final SubscriptionManager subMgr = (SubscriptionManager) context
+ .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ final TelecomManager telecomMgr =
+ (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+ final TelephonyManager telephonyMgr =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ PhoneAccount phoneAccount = telecomMgr.getPhoneAccount(phoneAccountHandle);
+
+ if (phoneAccount == null) {
+ return null;
+ }
+
+ CallMethodInfo callMethodInfo = new CallMethodInfo();
+
+ callMethodInfo.mComponent = phoneAccountHandle.getComponentName();
+ callMethodInfo.mId = phoneAccountHandle.getId();
+ callMethodInfo.mUserHandle = phoneAccountHandle.getUserHandle();
+ callMethodInfo.mSubId = telephonyMgr.getSubIdForPhoneAccount(phoneAccount);
+ callMethodInfo.mSlotId = SubscriptionManager.getSlotId(callMethodInfo.mSubId);
+ callMethodInfo.mName = getPhoneAccountName(context, phoneAccount, callMethodInfo.mSlotId);
+ callMethodInfo.mColor =
+ getPhoneAccountColor(subMgr.getActiveSubscriptionInfo(callMethodInfo.mSubId));
+ callMethodInfo.mIsInCallProvider = false;
+
+ final int simState = telephonyMgr.getSimState(callMethodInfo.mSlotId);
+ if ((simState == TelephonyManager.SIM_STATE_ABSENT) ||
+ (simState == TelephonyManager.SIM_STATE_UNKNOWN)) {
+ return null;
+ }
+
+ return callMethodInfo;
+ }
+
+ /*
+ * Look up the currently logged in plugin account in case plugin fails to return a valid
+ * account handle
+ */
+ public static String lookupAccountHandle(Context context, String targetAccountType) {
+ // Gather account handles logged into the device as a backup, in case
+ // plugins fail to return the account handle even when it reports its
+ // state as authenticated
+ AccountTypeManager accountTypes = AccountTypeManager.getInstance(context);
+ List<AccountWithDataSet> accounts = accountTypes.getAccounts(false);
+ ArrayMap<String, String> accountMap = new ArrayMap<>();
+
+ for (AccountWithDataSet account : accounts) {
+ AccountType accountType =
+ accountTypes.getAccountType(account.type, account.dataSet);
+ if (accountType.isExtension() && !account.hasData(context)) {
+ // Hide extensions with no raw_contacts.
+ continue;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "account.type: " + account.type + "account.name: " + account.name);
+ }
+ // currently only handle one account per account type use case
+ accountMap.put(account.type, account.name);
+ }
+ return accountMap.containsKey(targetAccountType) ? accountMap.get(targetAccountType) : "";
+ }
+
+ /*
+ * Return if the plugin is a soft logged-out state (authenticated is false and there's still
+ * an account saved in account manager)
+ */
+ public static boolean isSoftLoggedOut(Context context, CallMethodInfo cmi) {
+ return (!cmi.mIsAuthenticated && !TextUtils.isEmpty(lookupAccountHandle(context,
+ cmi.mAccountType)));
+ }
+
+
+ public static StartInCallCallReceiver getVoIPResultReceiver(Context context, CallMethodInfo cmi,
+ String originCode) {
+ return getVoIPResultReceiver(context, cmi, originCode, null);
+ }
+
+ public static StartInCallCallReceiver getVoIPResultReceiver(final Context context,
+ final CallMethodInfo cmi, final String originCode,
+ final StartInCallCallReceiver.InCallCallListener listener) {
+
+ 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 = context.getResources()
+ .getString(R.string.invalid_number_text);
+ text = String.format(text, cmi.mName);
+ Toast.makeText(context, text, Toast.LENGTH_LONG).show();
+ break;
+ default:
+ Log.i(TAG, "Nothing to do for this Start VoIP Call resultcode = "
+ + resultCode);
+ break;
+ }
+ if (listener != null) {
+ listener.onResult(resultCode);
+ }
+ }
+
+ });
+
+ return svcrr;
+ }
+}
diff --git a/src-ambient/com/android/phone/common/incall/utils/MimeTypeUtils.java b/src-ambient/com/android/phone/common/incall/utils/MimeTypeUtils.java
new file mode 100644
index 0000000..8f0c260
--- /dev/null
+++ b/src-ambient/com/android/phone/common/incall/utils/MimeTypeUtils.java
@@ -0,0 +1,143 @@
+/*
+ * 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.phone.common.incall.utils;
+
+import android.content.Context;
+
+import com.android.phone.common.ambient.AmbientDataSubscription;
+import com.android.phone.common.incall.CallMethodInfo;
+import com.android.phone.common.incall.api.ApiHelper;
+import com.cyanogen.ambient.plugin.PluginStatus;
+import com.google.common.base.Joiner;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static com.android.phone.common.ambient.TypedPendingResult.GENERAL_MIME_TYPE;
+import static com.android.phone.common.ambient.TypedPendingResult.IM_MIME_TYPE;
+import static com.android.phone.common.ambient.TypedPendingResult.VIDEO_MIME_TYPE;
+
+
+/**
+ * Class with logic to get mimes and mime types.
+ */
+public class MimeTypeUtils extends ApiHelper {
+
+ private static String getMimeTypes(AmbientDataSubscription<CallMethodInfo> instance,
+ boolean enabledOnly, int mimeID) {
+
+ String mimeTypes = "";
+ List<String> mimeTypesList = new ArrayList<>();
+ for (CallMethodInfo cmi : instance.getPluginInfo().values()) {
+ String mimeType = null;
+ switch(mimeID) {
+ case GENERAL_MIME_TYPE:
+ mimeType = cmi.mMimeType;
+ break;
+ case IM_MIME_TYPE:
+ mimeType = cmi.mVideoCallableMimeType;
+ break;
+ case VIDEO_MIME_TYPE:
+ mimeType = cmi.mImMimeType;
+ }
+
+ if (!enabledOnly) {
+ mimeTypesList.add(mimeType);
+ continue;
+ }
+
+ if (cmi.mStatus == PluginStatus.ENABLED) {
+ mimeTypesList.add(mimeType);
+ }
+ }
+
+ 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 all (not limited to enabled) mime types
+ */
+ public static String getAllMimeTypes(AmbientDataSubscription<CallMethodInfo> instance) {
+ return getMimeTypes(instance, false, GENERAL_MIME_TYPE);
+ }
+
+ /**
+ * 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(AmbientDataSubscription<CallMethodInfo> instance) {
+ return getMimeTypes(instance, true, GENERAL_MIME_TYPE);
+ }
+
+ /**
+ * A few items need a list of video callable 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 video callable mime types
+ */
+ public static String getAllEnabledVideoCallableMimeTypes(
+ AmbientDataSubscription<CallMethodInfo> instance) {
+ return getMimeTypes(instance, true, VIDEO_MIME_TYPE);
+ }
+
+ public static String getAllEnabledImMimeTypes(
+ AmbientDataSubscription<CallMethodInfo> instance) {
+ return getMimeTypes(instance, true, IM_MIME_TYPE);
+ }
+
+ public static Set<String> getAllEnabledVideoImMimeSet(
+ AmbientDataSubscription<CallMethodInfo> instance) {
+ String[] videoMimes = getAllEnabledVideoCallableMimeTypes(instance).split(",");
+ String[] imMimes = getAllEnabledImMimeTypes(instance).split(",");
+ HashSet<String> mimeSet = new HashSet<>();
+
+ if (videoMimes != null) {
+ mimeSet.addAll(Arrays.asList(videoMimes));
+ }
+ if (imMimes != null) {
+ mimeSet.addAll(Arrays.asList(imMimes));
+ }
+ return mimeSet;
+ }
+
+ public static Set<String> getAllEnabledVoiceMimeSet(
+ AmbientDataSubscription<CallMethodInfo> instance) {
+ String[] mimes = getAllEnabledMimeTypes(instance).split(",");
+ HashSet<String> mimeSet = new HashSet<>();
+ if (mimes != null) {
+ mimeSet.addAll(Arrays.asList(mimes));
+ }
+ return mimeSet;
+ }
+
+
+}
diff --git a/src-ambient/com/android/phone/common/nudge/api/ApiHelper.java b/src-ambient/com/android/phone/common/nudge/api/ApiHelper.java
new file mode 100644
index 0000000..d2ca30f
--- /dev/null
+++ b/src-ambient/com/android/phone/common/nudge/api/ApiHelper.java
@@ -0,0 +1,31 @@
+/*
+ * 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.phone.common.nudge.api;
+
+import com.cyanogen.ambient.discovery.NudgeApi;
+import com.cyanogen.ambient.discovery.NudgeServices;
+
+/**
+ * Helper for getting Nudge Api
+ */
+public class ApiHelper {
+
+ static NudgeApi thisApi() {
+ return NudgeServices.NudgeApi;
+ }
+
+}
diff --git a/src-ambient/com/android/phone/common/nudge/api/NudgeQueries.java b/src-ambient/com/android/phone/common/nudge/api/NudgeQueries.java
new file mode 100644
index 0000000..a48e079
--- /dev/null
+++ b/src-ambient/com/android/phone/common/nudge/api/NudgeQueries.java
@@ -0,0 +1,55 @@
+/*
+ * 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.phone.common.nudge.api;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.phone.common.ambient.TypedPendingResult;
+import com.android.phone.common.nudge.utils.NudgeUtils;
+import com.cyanogen.ambient.common.api.AmbientApiClient;
+import com.cyanogen.ambient.discovery.NudgeServices;
+
+/**
+ * Queries for Nudges
+ */
+public class NudgeQueries extends ApiHelper {
+
+ private static TypedPendingResult getNudgeForKey(AmbientApiClient client,
+ ComponentName componentName, String key) {
+ int type = NudgeTypedResult.getNudgeTypeForKey(key);
+ return new TypedPendingResult(thisApi().getConfigurationForKey(
+ client, componentName, key), type);
+ }
+
+ /**
+ * Get a nudge configuration for the mod
+ * @param cn of mod
+ *
+ * @return TypedPendingResult to identify which field to modify in our CallMethodInfo object
+ */
+ public static TypedPendingResult getNudgeConfig(AmbientApiClient client,
+ Context c, ComponentName cn, String key) {
+
+ ComponentName nudgeComponent = NudgeUtils.getNudgeComponent(c, cn);
+ if (nudgeComponent != null) {
+ return NudgeQueries.getNudgeForKey(client, nudgeComponent, key);
+ }
+ return null;
+ }
+}
diff --git a/src-ambient/com/android/phone/common/nudge/api/NudgeTypedResult.java b/src-ambient/com/android/phone/common/nudge/api/NudgeTypedResult.java
new file mode 100644
index 0000000..39a9d19
--- /dev/null
+++ b/src-ambient/com/android/phone/common/nudge/api/NudgeTypedResult.java
@@ -0,0 +1,51 @@
+/*
+ * 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.phone.common.nudge.api;
+
+import com.android.phone.common.ambient.TypedPendingResult;
+import com.cyanogen.ambient.common.api.PendingResult;
+import com.cyanogen.ambient.discovery.util.NudgeKey;
+
+import android.util.Log;
+
+/**
+ * InCall Pending result types.
+ */
+public class NudgeTypedResult extends TypedPendingResult {
+
+ private static final String TAG = NudgeTypedResult.class.getSimpleName();
+
+ public NudgeTypedResult(PendingResult pendingResult, int type) {
+ super(pendingResult, type);
+ }
+
+ public static int getNudgeTypeForKey(String key) {
+ switch (key) {
+ case NudgeKey.INCALL_CREDIT_NUDGE:
+ return INCALL_CREDIT_NUDGE;
+ case NudgeKey.INCALL_CONTACT_CARD_LOGIN:
+ return INCALL_CONTACT_CARD_LOGIN;
+ case NudgeKey.INCALL_CONTACT_CARD_DOWNLOAD:
+ return INCALL_CONTACT_CARD_DOWNLOAD;
+ case NudgeKey.INCALL_CONTACT_FRAGMENT_LOGIN:
+ return INCALL_CONTACT_FRAGMENT_LOGIN;
+ default:
+ Log.e(TAG, "No nudge type!");
+ return NONE;
+ }
+ }
+}
diff --git a/src-ambient/com/android/phone/common/nudge/utils/NudgeUtils.java b/src-ambient/com/android/phone/common/nudge/utils/NudgeUtils.java
new file mode 100644
index 0000000..921c97f
--- /dev/null
+++ b/src-ambient/com/android/phone/common/nudge/utils/NudgeUtils.java
@@ -0,0 +1,47 @@
+/*
+ * 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.phone.common.nudge.utils;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+
+import java.util.List;
+
+/**
+ * Utilities class to help with nudge events.
+ */
+public class NudgeUtils {
+
+ public static final String NUDGE_PROVIDER_INTENT = "cyanogen.service.NUDGE_PROVIDER";
+
+ public static ComponentName getNudgeComponent(Context context, final ComponentName cn) {
+ // find a nudge component if it exists for this package
+ Intent nudgeIntent = new Intent(NUDGE_PROVIDER_INTENT);
+ nudgeIntent.setPackage(cn.getPackageName());
+ List<ResolveInfo> resolved = context.getPackageManager()
+ .queryIntentServices(nudgeIntent, 0);
+ if (resolved != null && !resolved.isEmpty()) {
+ ResolveInfo result = resolved.get(0);
+ return new ComponentName(result.serviceInfo.applicationInfo.packageName,
+ result.serviceInfo.name);
+ }
+ // if a nudge component doesn't exist, just finish here
+ return null;
+ }
+}