summaryrefslogtreecommitdiffstats
path: root/src-ambient/com/android/phone/common/ambient
diff options
context:
space:
mode:
Diffstat (limited to 'src-ambient/com/android/phone/common/ambient')
-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
5 files changed, 526 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;
+ }
+ }
+
+}