Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2366dedf authored by Stephen Bird's avatar Stephen Bird
Browse files

Improvements to Nudges

- Fix bugs found in test cases
- Make nudge lookup more performant
- Make nudge metrics work

Change-Id: I1e75dc3a2bdda5cc5763c327bfd1b94c6aa6e735
parent 9e8223e4
Loading
Loading
Loading
Loading
+3 −5
Original line number Diff line number Diff line
@@ -32,9 +32,7 @@
        <meta-data android:name="com.cyanogen.ambient.analytics.key"
                   android:value="6l2dXt9DFioFa1Mfb4eZsM9l87Rl9hp1UL75tO9w"/>

        <receiver android:name=".incall.CallMethodStatusReceiver"
                  android:enabled="true"
                  android:exported="true">
        <receiver android:name=".incall.CallMethodStatusReceiver">
            <intent-filter>
                <action android:name="cyanogen.ambient.core.plugin.incall.action.plugin_status_changed"/>
            </intent-filter>
@@ -43,9 +41,9 @@
        <receiver android:name=".discovery.DiscoverySignalReceiver">
            <intent-filter>
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
                <action android:name="com.android.dialer.discovery.DiscoverySignalReceiver.ON_SHOW" />
                <action android:name="com.android.dialer.discovery.DiscoverySignalReceiver.ON_DISMISS" />
            </intent-filter>
        </receiver>

+152 −110
Original line number Diff line number Diff line
@@ -24,11 +24,16 @@ import com.cyanogen.ambient.discovery.NudgeServices;
import com.cyanogen.ambient.discovery.nudge.NotificationNudge;
import com.cyanogen.ambient.discovery.nudge.Nudge;
import com.cyanogen.ambient.discovery.util.NudgeKey;
import com.cyanogen.ambient.discovery.results.NudgablePluginsResult;
import com.cyanogen.ambient.incall.InCallApi;
import com.cyanogen.ambient.incall.InCallServices;
import com.cyanogen.ambient.incall.results.InCallProviderInfoResult;
import com.cyanogen.ambient.incall.results.PluginStatusResult;
import com.cyanogen.ambient.incall.results.InstalledPluginsResult;

import com.cyanogen.ambient.plugin.PluginStatus;
import com.cyanogen.ambient.common.api.ResultCallback;


import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -37,164 +42,202 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *  A small, lean, nudge'r. For loading data from mods that can provide nudges.
 *
 *  All Dialer nudge events route through here, and if found worthy, will send a nudge to
 *  the Discovery Nudge Service in ModCore.
 */
public class DiscoveryEventHandler {

    private static final String TAG = "DiscoveryEventHandler";
    private static final boolean DEBUG_STATUS = false;

    public static void getNudgeProvidersWithKey(final Context context, final String key) {
        getNudgeProvidersWithKey(context, key, false);
    }

    public static void getNudgeProvidersWithKey(final Context context, final String key, final
                                                boolean isTesting) {
        // Spin up a new thread to make the needed calls to ambient.
        new Thread(new Runnable() {
            @Override
            public void run() {
                AmbientApiClient client = AmbientConnection.CLIENT.get(context);
                ArrayList<NotificationNudge> nudges = getNudges(client, context, key,
                        isTesting);
                sendNudgeRequestToDiscovery(client, nudges);
            }
        }).start();
    }
    private Context mContext;
    private AmbientApiClient mClient;
    private HashMap<String, Bundle> availableNudges = new HashMap<String, Bundle>();
    private List<ComponentName> plugins = new ArrayList<ComponentName>();

    private static void sendNudgeRequestToDiscovery(AmbientApiClient client,
                                                   ArrayList<NotificationNudge> nudges) {
    // Key for this instance
    private static String mKey;

        for (NotificationNudge nn : nudges) {
            DiscoveryManagerServices.DiscoveryManagerApi.publishNudge(client, nn);
        }
    public DiscoveryEventHandler(Context context) {
        mContext = context;
        mClient = AmbientConnection.CLIENT.get(context);
    }

    private static ArrayList<NotificationNudge> getNudges(AmbientApiClient client, Context context,
                                                         String key, boolean isTesting) {

        Map nudgePlugins =
                NudgeServices.NudgeApi.getAvailableNudgesForKey(client, key).await().components;
    public void getNudgeProvidersWithKey(final String key) {
        getNudgeProvidersWithKey(key, false);
    }

        ArrayList<NotificationNudge> notificationNudges = new ArrayList<>();
    /* package */ void getNudgeProvidersWithKey(final String key, final boolean isTesting) {
        mKey = key;
        getAvailableNudgesForKey(key, isTesting);
        getInstalledPlugins();
    }

        if (nudgePlugins == null) {
            return notificationNudges;
    private void getAvailableNudgesForKey(final String key, final boolean isTesting) {
        NudgeServices.NudgeApi.getAvailableNudgesForKey(mClient, key)
                .setResultCallback(new ResultCallback<NudgablePluginsResult>() {
                    @Override
                    public void onResult(NudgablePluginsResult plugins) {
                        Map nudgePlugins = plugins.components;
                        if (nudgePlugins == null || nudgePlugins.size() == 0) {
                            return;
                        }

        InCallApi api = InCallServices.getInstance();
                        for (Object entry : nudgePlugins.entrySet()) {
                            Map.Entry<ComponentName, Bundle> theEntry
                                    = (Map.Entry<ComponentName, Bundle>) entry;

        List<ComponentName> plugins = api.getInstalledPlugins(client).await().components;
                            Bundle b = theEntry.getValue();

        HashMap<String, Bundle> availableNudges = new HashMap<>();
                            if (validateShouldShowNudge(key, b) && !isTesting) {
                                // Nudge not yet ready for this item.
                                continue;
                            }

                            availableNudges.put(theEntry.getKey().getPackageName(), b);
                        }

        for (Object entry : nudgePlugins.entrySet()) {
            Map.Entry<ComponentName, Bundle> theEntry = (Map.Entry<ComponentName, Bundle>) entry;
            availableNudges.put(theEntry.getKey().getPackageName(), theEntry.getValue());
                        getStatusWhenReady();
                    }
                });
    }

        if (plugins != null && plugins.size() > 0) {

            for (ComponentName component : plugins) {
    /**
     * Get installed plugins
     */
    private void getInstalledPlugins() {
        InCallServices.getInstance().getInstalledPlugins(mClient)
                .setResultCallback(new ResultCallback<InstalledPluginsResult>() {
                    @Override
                    public void onResult(InstalledPluginsResult installedPluginsResult) {
                        plugins = installedPluginsResult.components;

                if (availableNudges.containsKey(component.getPackageName())) {
                        if (plugins == null || plugins.size() == 0) {
                            return;
                        }
                        getStatusWhenReady();
                    }
                });
    }

                    PluginStatusResult statusResult =
                            api.getPluginStatus(client, component).await();
    /**
     * Get our plugin enabled status
     * @param cn
     */
    private void getCallMethodStatus(final ComponentName cn) {
        InCallServices.getInstance().getPluginStatus(mClient, cn)
            .setResultCallback(new ResultCallback<PluginStatusResult>() {
                @Override
                public void onResult(PluginStatusResult pluginStatusResult) {

                    Bundle b = availableNudges.get(component.getPackageName());
                    boolean pluginIsApplicable = pluginStatusResult.status != PluginStatus.DISABLED
                            && pluginStatusResult.status != PluginStatus.UNAVAILABLE;

                    if (validateShouldShowNudge(key, context, b) && !isTesting) {
                        // Nudge not yet ready for this item.
                        continue;
                    if (!pluginIsApplicable) {
                        plugins.remove(cn);
                        return;
                    }

                    if (DEBUG_STATUS || (statusResult.status != PluginStatus.DISABLED &&
                                statusResult.status != PluginStatus.UNAVAILABLE)) {

                        Bitmap notificationIcon = null;

                        InCallProviderInfoResult providerInfo =
                                api.getProviderInfo(client, component).await();
                    getCallMethodIcon(cn);
                }
            });
    }

    private void getCallMethodIcon(final ComponentName cn) {
        InCallServices.getInstance().getProviderInfo(mClient, cn)
                .setResultCallback(new ResultCallback<InCallProviderInfoResult>() {
                    @Override
                    public void onResult(InCallProviderInfoResult providerInfo) {
                        if (providerInfo != null && providerInfo.inCallProviderInfo != null) {
                            try {
                                Resources pluginResources = context.getPackageManager()
                                        .getResourcesForApplication(component.getPackageName());
                                Resources pluginResources = mContext.getPackageManager()
                                        .getResourcesForApplication(cn.getPackageName());

                                Drawable d = pluginResources.getDrawable(
                                        providerInfo.inCallProviderInfo.getBrandIcon(), null);

                                notificationIcon = ImageUtils.drawableToBitmap(d);
                                createNudge(cn, ImageUtils.drawableToBitmap(d));
                            } catch (Resources.NotFoundException e) {
                                Log.e(TAG, "Unable to retrieve icon for plugin: " + component);
                                Log.e(TAG, "Unable to retrieve icon for plugin: " + cn);
                            } catch (PackageManager.NameNotFoundException e) {
                                Log.e(TAG, "Plugin isn't installed: " + component);
                                Log.e(TAG, "Plugin isn't installed: " + cn);
                            }
                        }
                    }
        });
    }

                        NotificationNudge nn = new NotificationNudge(component.getPackageName(),
                                Nudge.Type.IMMEDIATE,
                                b.getString(NudgeKey.NUDGE_PARAM_TITLE),
                                b.getString(NudgeKey.NOTIFICATION_PARAM_BODY));

                        if (notificationIcon != null) {
                            nn.setLargeIcon(notificationIcon);
    private void getStatusWhenReady() {
        if (plugins == null || plugins.size() == 0
                || availableNudges == null || availableNudges.size() == 0) {
            // Not ready or never will be, just bail man.
            return;
        }
        for (ComponentName cn : plugins) {
            if (availableNudges.containsKey(cn.getPackageName())) {
                getCallMethodStatus(cn);
            }
        }
    }

                        Parcelable[] actions =
                                b.getParcelableArray(NudgeKey.NOTIFICATION_PARAM_NUDGE_ACTIONS);
    private void createNudge(ComponentName component, Bitmap notificationIcon) {
        Bundle b = availableNudges.get(component.getPackageName());

        String title = b.getString(NudgeKey.NUDGE_PARAM_TITLE);
        String body = b.getString(NudgeKey.NOTIFICATION_PARAM_BODY);
        Parcelable[] actions = b.getParcelableArray(NudgeKey.NOTIFICATION_PARAM_NUDGE_ACTIONS);

        NotificationNudge nn = new NotificationNudge(component.getPackageName(),
                Nudge.Type.IMMEDIATE, title, body);

        for (Parcelable action : actions) {
            NotificationNudge.Button button = (NotificationNudge.Button) action;
            nn.addButton(button);
        }

                        Intent intent = new Intent(context, DiscoverySignalReceiver.class);
                        intent.setAction(DiscoverySignalReceiver.DISCOVERY_NUDGE_SHOWN);
        nn.setLargeIcon(notificationIcon);
        nn.setOnShowIntent(buildActionIntent(body,
                DiscoverySignalReceiver.DISCOVERY_NUDGE_SHOWN, component));
        nn.setContentIntent(buildActionIntent(body,
                DiscoverySignalReceiver.DISCOVERY_NUDGE_DISMISS, component));

        DiscoveryManagerServices.DiscoveryManagerApi.publishNudge(mClient, nn);
    }

    private PendingIntent buildActionIntent(String body, String action, ComponentName component) {
        Intent intent = new Intent(action);
        String nudgeID;
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
                            messageDigest.update(b.getString(NudgeKey.NOTIFICATION_PARAM_BODY)
                                    .getBytes());
            messageDigest.update(body.getBytes());
            nudgeID = new String(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            Log.e(TAG, "No Algo, defaulting to unknown", e);
                            nudgeID = "unkown";
            nudgeID = "unknown";
        }
        intent.putExtra(DiscoverySignalReceiver.NUDGE_ID, nudgeID);
        intent.putExtra(DiscoverySignalReceiver.NUDGE_KEY, mKey);
        intent.putExtra(DiscoverySignalReceiver.NUDGE_COMPONENT, component.flattenToShortString());

                        intent.putExtra(DiscoverySignalReceiver.NUDGE_KEY, key);

                        intent.putExtra(DiscoverySignalReceiver.NUDGE_COMPONENT,
                                component.flattenToShortString());

                        PendingIntent pendingIntent = PendingIntent.getBroadcast(
                                context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

                        nn.setOnShowIntent(pendingIntent);

                        notificationNudges.add(nn);
                    }
                }
            }
        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
    }
        return notificationNudges;
    }

    private static boolean validateShouldShowNudge(String key, Context c, Bundle b) {

        boolean checkCount = false;
    private boolean validateShouldShowNudge(String key, Bundle b) {
        boolean checkCount;

        SharedPreferences preferences = c.getSharedPreferences(DialtactsActivity.SHARED_PREFS_NAME,
                        Context.MODE_PRIVATE);
        SharedPreferences preferences = mContext.getSharedPreferences(DialtactsActivity
                .SHARED_PREFS_NAME, Context.MODE_PRIVATE);

        int count = 0;

        // The count starts at 1 here because this is the first time we've seen this item.
        if (key.equals(NudgeKey.NOTIFICATION_INTERNATIONAL_CALL)) {
            count = preferences.getInt(CallMethodUtils.PREF_INTERNATIONAL_CALLS, 0);
            count = preferences.getInt(CallMethodUtils.PREF_INTERNATIONAL_CALLS, 1);
        } else if (key.equals(NudgeKey.NOTIFICATION_WIFI_CALL)) {
            count = preferences.getInt(CallMethodUtils.PREF_WIFI_CALL, 0);
            count = preferences.getInt(CallMethodUtils.PREF_WIFI_CALL, 1);
        }

        checkCount =
@@ -203,7 +246,6 @@ public class DiscoveryEventHandler {

        // return true if nudge should be shown
        return checkCount;

    }

}
+8 −4
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ package com.android.dialer.discovery;
import android.app.IntentService;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.text.TextUtils;

import com.android.dialer.discovery.DiscoveryEventHandler;
import com.cyanogen.ambient.discovery.util.NudgeKey;
@@ -19,15 +20,18 @@ public class DiscoveryService extends IntentService {
    @Override
    protected void onHandleIntent(Intent intent) {
        String action = intent.getAction();
        String nudgeKey = null;
        switch (action) {
            case ConnectivityManager.CONNECTIVITY_ACTION:
                DiscoveryEventHandler.getNudgeProvidersWithKey(getApplicationContext(),
                        NudgeKey.NOTIFICATION_ROAMING);
                nudgeKey = NudgeKey.NOTIFICATION_ROAMING;
                break;
            case Intent.ACTION_NEW_OUTGOING_CALL:
                DiscoveryEventHandler.getNudgeProvidersWithKey(getApplicationContext(),
                        NudgeKey.NOTIFICATION_INTERNATIONAL_CALL);
                nudgeKey = NudgeKey.NOTIFICATION_INTERNATIONAL_CALL;
                break;
        }
        if (!TextUtils.isEmpty(nudgeKey)) {
            new DiscoveryEventHandler(getApplicationContext()).getNudgeProvidersWithKey(nudgeKey);
        }

    }
}
+40 −16
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.telephony.TelephonyManager;

import android.text.TextUtils;
import android.util.Log;
import com.android.contacts.common.GeoUtil;
import com.android.dialer.DialtactsActivity;
@@ -40,9 +41,20 @@ public class DiscoverySignalReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        String action = intent.getAction();

        String nudgeId = intent.getStringExtra(NUDGE_ID);
        String nudgeKey = intent.getStringExtra(NUDGE_KEY);

        ComponentName nudgeComponent = null;
        if (intent.hasExtra(NUDGE_COMPONENT)
                && !TextUtils.isEmpty(intent.getStringExtra(NUDGE_COMPONENT))) {

            nudgeComponent
                    = ComponentName.unflattenFromString(intent.getStringExtra(NUDGE_COMPONENT));

        }

        switch (action) {
            case ConnectivityManager.CONNECTIVITY_ACTION:
                ConnectivityManager connManager = (ConnectivityManager)
@@ -70,10 +82,10 @@ public class DiscoverySignalReceiver extends BroadcastReceiver {
                }
                break;
            case DISCOVERY_NUDGE_SHOWN:
                String nudgeId = intent.getStringExtra(NUDGE_ID);
                String nudgeKey = intent.getStringExtra(NUDGE_KEY);
                ComponentName nudgeComponent =
                        ComponentName.unflattenFromString(intent.getStringExtra(NUDGE_COMPONENT));
                if (nudgeComponent == null) {
                    Log.e(TAG, "The nudge component is null, not counting as a nudge event");
                    return;
                }

                SharedPreferences sp = context.getSharedPreferences(NUDGE_SHARED_PREF,
                        Context.MODE_PRIVATE);
@@ -92,26 +104,38 @@ public class DiscoverySignalReceiver extends BroadcastReceiver {
                editor.putLong(timeKey, System.currentTimeMillis());
                editor.apply();

                InCallMetricsHelper.Events event;
                if (nudgeKey.equals(NudgeKey.NOTIFICATION_INTERNATIONAL_CALL)) {
                    event = InCallMetricsHelper.Events.NUDGE_EVENT_INTL;
                } else {
                    event = InCallMetricsHelper.Events.NUDGE_EVENT_ROAMING;
                }

                InCallMetricsHelper.increaseCountOfMetric(nudgeComponent, event,
                        InCallMetricsHelper.Categories.DISCOVERY_NUDGES,
                recordDiscoveryCount(nudgeComponent, nudgeKey,
                        InCallMetricsHelper.Parameters.COUNT);

                break;
            case DISCOVERY_NUDGE_DISMISS:
        }

    }

    private void recordDiscoveryCount(ComponentName componentName, String nudgeKey,
                                      InCallMetricsHelper.Parameters param) {
        InCallMetricsHelper.Events event = null;
        switch(nudgeKey) {
            case NudgeKey.NOTIFICATION_INTERNATIONAL_CALL:
                event = InCallMetricsHelper.Events.NUDGE_EVENT_INTL;
                break;
            case NudgeKey.NOTIFICATION_WIFI_CALL:
                event = InCallMetricsHelper.Events.NUDGE_EVENT_WIFI;
                break;
            case NudgeKey.NOTIFICATION_ROAMING:
                event = InCallMetricsHelper.Events.NUDGE_EVENT_ROAMING;
                break;
        }

        if (event != null) {
            InCallMetricsHelper.increaseCountOfMetric(componentName, event,
                    InCallMetricsHelper.Categories.DISCOVERY_NUDGES,
                    param);
        }
    }

    public boolean isMaybeInternationalNumber(Context context, String number) {

    private boolean isMaybeInternationalNumber(Context context, String number) {
        PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
        String currentCountryIso = GeoUtil.getCurrentCountryIso(context);

+3 −3
Original line number Diff line number Diff line
@@ -58,13 +58,13 @@ public class NudgeItem {
    public String getTimeKey() {
        String[] key = getBaseKey();
        key[DATA] = TIME;
        return Joiner.on(InCallMetricsHelper.DELIMIT).join(key);
        return Joiner.on(InCallMetricsHelper.DELIMIT).skipNulls().join(key);
    }

    public String getWinKey() {
        String[] key = getBaseKey();
        key[DATA] = NUDGE_ENABLED_PLUGIN;
        return Joiner.on(InCallMetricsHelper.DELIMIT).join(key);
        return Joiner.on(InCallMetricsHelper.DELIMIT).skipNulls().join(key);
    }

    /**
@@ -73,7 +73,7 @@ public class NudgeItem {
    public String getCountKey() {
        String[] key = getBaseKey();
        key[DATA] = InCallMetricsHelper.Parameters.COUNT.value();
        return Joiner.on(InCallMetricsHelper.DELIMIT).join(key);
        return Joiner.on(InCallMetricsHelper.DELIMIT).skipNulls().join(key);
    }

    public static String getKeyType(String[] array) {
Loading