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

Commit 7be22e62 authored by Artem Shvadskiy's avatar Artem Shvadskiy Committed by Rohit Yengisetty
Browse files

Add Caller-info provider nudges and adapt new CallerInfoApi

CallerInfoApi within AmbientSDK has undergone a revision. There
are changes to authentication APIs for Plugins and also to the
LookupProvider APIs.

Change-Id: Ia247925c7797e290ec05214bdca27ec70e41abda
issue-id: DIALER-719 DIALER-724
parent 92e801cd
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -74,5 +74,8 @@
            </intent-filter>
        </service>

        <service android:name=".callerinfo.ProviderActivationService"
                 android:exported="false"/>

    </application>
</manifest>
+7 −2
Original line number Diff line number Diff line
@@ -63,6 +63,7 @@ import com.android.dialer.widget.DialerQuickContact;
import com.android.phone.common.incall.CallMethodInfo;

import com.cyanogen.ambient.incall.extension.OriginCodes;
import com.cyanogen.lookup.phonenumber.contract.LookupProvider;
import com.cyanogen.lookup.phonenumber.provider.LookupProviderImpl;

/**
@@ -222,6 +223,7 @@ public class CallDetailActivity extends Activity
    private TextView mCallerNumber;
    private TextView mAccountLabel;
    private View mCallButton;
    private LookupProvider mLookupProvider;
    private ContactInfoHelper mContactInfoHelper;
    private BlockContactHelper mBlockContactHelper;

@@ -285,8 +287,10 @@ public class CallDetailActivity extends Activity
            }
        });

        mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this));
        mBlockContactHelper = new BlockContactHelper(this, new LookupProviderImpl(this));
        mBlockContactHelper = new BlockContactHelper(this);
        mLookupProvider = LookupProviderImpl.INSTANCE.get(this);
        mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this),
                mLookupProvider);
        getActionBar().setDisplayHomeAsUpEnabled(true);

        if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) {
@@ -297,6 +301,7 @@ public class CallDetailActivity extends Activity
    @Override
    protected void onDestroy() {
        super.onDestroy();
        LookupProviderImpl.INSTANCE.release();
        mBlockContactHelper.destroy();
        mCallRecordingDataStore.close();
    }
+3 −0
Original line number Diff line number Diff line
@@ -76,6 +76,7 @@ import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.common.widget.FloatingActionButtonController;
import com.android.contacts.commonbind.analytics.AnalyticsUtil;
import com.android.dialer.callerinfo.CallerInfoProviderPicker;
import com.android.dialer.calllog.CallLogActivity;
import com.android.dialer.calllog.CallLogFragment;
import com.android.dialer.database.DialerDatabaseHelper;
@@ -640,6 +641,8 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O
        callStateIntentFilter.addAction(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE);
        callStateIntentFilter.addAction(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED);
        registerReceiver(mCallStateReceiver, callStateIntentFilter);

        CallerInfoProviderPicker.onAppLaunched(this);
    }

    @Override
+148 −108
Original line number Diff line number Diff line
@@ -14,140 +14,180 @@

package com.android.dialer.callerinfo;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.graphics.Bitmap;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.android.dialer.R;
import com.android.dialer.util.MetricsHelper;
import com.android.dialer.util.ImageUtils;
import com.android.phone.common.ambient.AmbientConnection;
import com.cyanogen.ambient.callerinfo.util.CallerInfoHelper;
import com.cyanogen.ambient.callerinfo.util.ProviderInfo;
import com.cyanogen.ambient.callerinfo.CallerInfoServices;

public class CallerInfoProviderPicker extends Activity {
import com.cyanogen.ambient.common.CyanogenAmbientUtil;
import com.cyanogen.ambient.common.api.ResultCallback;
import com.cyanogen.ambient.discovery.DiscoveryManagerServices;
import com.cyanogen.ambient.discovery.nudge.DialogNudge;
import com.cyanogen.ambient.discovery.results.BooleanResult;

    private static final String TAG = "CallerInfoProviderPicker";
public class CallerInfoProviderPicker {
    private static final String NUDGE_ID = "callerInfoPickerDialogNudge";

    public static final String EXTRA_PROVIDER_INFO = "extra_provider_info";
    public static final String EXTRA_METRICS_REASON = "extra_reason";
    public static final int REASON_FIRST_LAUNCH_DIALER = 0;
    public static final int REASON_INCOMING_CALL = 1;
    public static final int REASON_INCOMING_CALL_FINAL_PROMPT = 2;
    public static final int REASON_DIALER_SETTINGS = 3;
    private static final int REQUEST_CODE_SUCCESS = 0;
    private static final int REQUEST_CODE_FAILURE = 1;

    private AlertDialog mDialog;
    private static final String PREF_FIRST_LAUNCH = "first_launch";
    private static final String PREF_UNKNOWN_CALL_COUNT = "unknown_call_count";
    private static final int UNKNOWN_CALL_FIRST_COUNT = 0;
    private static final int UNKNOWN_CALL_FINAL_COUNT = 4;

    public static void onAppLaunched(Context context) {
        if (checkPreconditions(context)) {
            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
            if (prefs.getBoolean(PREF_FIRST_LAUNCH, true)) {
                enableProvider(context, true, ProviderActivationService.REASON_FIRST_LAUNCH_DIALER,
                        new ResultCallback<BooleanResult>() {
                            @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final ComponentName provider = getIntent().getParcelableExtra(EXTRA_PROVIDER_INFO);
        final ProviderInfo providerInfo = CallerInfoHelper.getProviderInfo(this, provider);
        if (providerInfo == null) {
            finish();
            return;
                            public void onResult(BooleanResult result) {
                                // Don't count this as first launch,
                                // unless discovery actually shows our dialog.
                                if (result.bool) {
                                    prefs.edit().putBoolean(PREF_FIRST_LAUNCH, false).commit();
                                }
                            }
                        }
                );
            }

        final Resources res = getResources();
        View view = View.inflate(this, R.layout.callerinfo_provider_picker, null);
        ImageView logo = (ImageView) view.findViewById(R.id.logo);
        TextView description = (TextView) view.findViewById(R.id.description);
        TextView disclaimer = (TextView) view.findViewById(R.id.disclaimer);

        if (providerInfo.hasProperty(ProviderInfo.PROPERTY_NEEDS_CONTACTS)) {
            String text = res.getString(R.string.callerinfo_provider_auth_access, providerInfo.getTitle());
            if (providerInfo.getPrivacyPolicyUrl() != null) {
                String learnMore = " <a href=\"%s\">%s</a>";
                text += String.format(learnMore, providerInfo.getPrivacyPolicyUrl(),
                        res.getString(R.string.callerinfo_provider_auth_learn_more));
        }
            disclaimer.setMovementMethod(LinkMovementMethod.getInstance());
            disclaimer.setText(Html.fromHtml(text));
        } else {
            disclaimer.setVisibility(View.GONE);
    }

        logo.setImageDrawable(providerInfo.getBrandLogo());
    public static void onUnknownCallEnded(Context context) {
        if (checkPreconditions(context)) {
            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
            final int currentCount = prefs.getInt(PREF_UNKNOWN_CALL_COUNT, 0);

        int resId = providerInfo.hasProperty(ProviderInfo.PROPERTY_SUPPORTS_SPAM) ?
                R.string.callerinfo_provider_auth_desc : R.string.callerinfo_provider_auth_desc_no_spam;
        description.setText(res.getString(resId, providerInfo.getTitle()));
            int metricsReason;
            if (currentCount == UNKNOWN_CALL_FIRST_COUNT) {
                metricsReason = ProviderActivationService.REASON_INCOMING_CALL_FIRST_PROMPT;
            } else if (currentCount == UNKNOWN_CALL_FINAL_COUNT) {
                metricsReason = ProviderActivationService.REASON_INCOMING_CALL_FINAL_PROMPT;
            } else {
                prefs.edit().putInt(PREF_UNKNOWN_CALL_COUNT, currentCount + 1).commit();
                return;
            }

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setView(view);
        builder.setPositiveButton(res.getString(R.string.callerinfo_provider_auth_yes),
                new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                sendMetrics(true, providerInfo.getPackageName());
                CallerInfoServices.CallerInfoApi.enablePlugin(
                        AmbientConnection.CLIENT.get(getApplicationContext()),
                        providerInfo.getComponentName());
                finish();
            }
        }).setNegativeButton(res.getString(R.string.callerinfo_provider_auth_no),
                new DialogInterface.OnClickListener() {
            enableProvider(context, true, metricsReason,
                    new ResultCallback<BooleanResult>() {
                        @Override
            public void onClick(DialogInterface dialog, int which) {
                sendMetrics(false, providerInfo.getPackageName());
                finish();
                        public void onResult(BooleanResult result) {
                            // Don't count this event, unless discovery actually shows our dialog.
                            if (result.bool) {
                                prefs.edit().putInt(PREF_UNKNOWN_CALL_COUNT, currentCount + 1).
                                        commit();
                            }
        }).setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                finish();
                        }
                    });
        mDialog = builder.create();
        mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                int buttonColor = res.getColor(R.color.callerinfo_provider_picker_negative_color);
                mDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(buttonColor);
        }
        });
        mDialog.show();
    }

    @Override
    public void onStop() {
        super.onStop();
    public static void onSettingEnabled(Context context) {
        enableProvider(context, false, ProviderActivationService.REASON_DIALER_SETTINGS, null);
    }

    void sendMetrics(boolean onAccept, String providerPackageName) {
        if (!getIntent().hasExtra(EXTRA_METRICS_REASON)) {
    private static void enableProvider(Context context, boolean withDialog, int metricsReason,
            ResultCallback<BooleanResult> callback) {
        CallerInfoHelper.ResolvedProvider[] providers =
                CallerInfoHelper.getInstalledProviders(context);
        if (providers.length == 0) {
            return;
        }
        int reason = getIntent().getIntExtra(EXTRA_METRICS_REASON, -1);
        MetricsHelper.Field field = new MetricsHelper.Field(
                MetricsHelper.Fields.PROVIDER_PACKAGE_NAME, providerPackageName);
        MetricsHelper.Actions action = onAccept ?
                MetricsHelper.Actions.OPTED_IN : MetricsHelper.Actions.OPTED_OUT;
        switch (reason) {
            case REASON_FIRST_LAUNCH_DIALER:
                MetricsHelper.sendEvent(MetricsHelper.Categories.PROVIDER_STATE_CHANGES,
                        action, MetricsHelper.State.FIRST_LAUNCH_DIALER, field);
                break;
            case REASON_INCOMING_CALL_FINAL_PROMPT:
                MetricsHelper.sendEvent(MetricsHelper.Categories.PROVIDER_STATE_CHANGES,
                        action, MetricsHelper.State.AFTER_FINAL_PROMPT, field);
                break;
            case REASON_INCOMING_CALL:
                MetricsHelper.sendEvent(MetricsHelper.Categories.PROVIDER_STATE_CHANGES,
                        action, MetricsHelper.State.AFTER_CALL_ENDED, field);
                break;
            case REASON_DIALER_SETTINGS:
                MetricsHelper.sendEvent(MetricsHelper.Categories.PROVIDER_STATE_CHANGES,
                        action, MetricsHelper.State.SETTINGS, field);
                break;

        // Assume only one provider
        ComponentName component = providers[0].getComponent();
        ProviderInfo info = CallerInfoHelper.getProviderInfo(context, component);

        if (withDialog) {
            showDialog(context, metricsReason, component, info, callback);
        } else {
            context.startService(buildEnableIntent(context, component, info, metricsReason));
        }
    }

    private static boolean checkPreconditions(Context context) {
        boolean setupCompleted = Settings.Secure.getInt(context.getContentResolver(),
                Settings.Secure.USER_SETUP_COMPLETE, 0) != 0;
        boolean ambientAvailable = CyanogenAmbientUtil.isCyanogenAmbientAvailable(context)
                == CyanogenAmbientUtil.SUCCESS;
        boolean providerEnabled = CallerInfoHelper.getActiveProviderInfo2(context) != null;
        boolean providerAvailable = CallerInfoHelper.getInstalledProviders(context).length > 0;
        return setupCompleted && ambientAvailable && !providerEnabled && providerAvailable;
    }

    private static void showDialog(Context context, int metricsReason, ComponentName component,
            ProviderInfo info, ResultCallback<BooleanResult> callback) {
        Resources res = context.getResources();
        CharSequence subText = null;
        if (info.hasProperty(ProviderInfo.PROPERTY_NEEDS_CONTACTS)) {
            String text = res.getString(R.string.callerinfo_provider_auth_access, info.getTitle());
            if (info.getPrivacyPolicyUrl() != null) {
                String learnMore = " <a href=\"%s\">%s</a>";
                text += String.format(learnMore, info.getPrivacyPolicyUrl(),
                        res.getString(R.string.callerinfo_provider_auth_learn_more));
            }
            subText = Html.fromHtml(text);
        }

        Bitmap logo = ImageUtils.drawableToBitmap(info.getBrandLogo());

        int resId = info.hasProperty(ProviderInfo.PROPERTY_SUPPORTS_SPAM) ?
                R.string.callerinfo_provider_auth_desc :
                R.string.callerinfo_provider_auth_desc_no_spam;
        String bodyText = res.getString(resId, info.getTitle());

        DialogNudge nudge = new DialogNudge(NUDGE_ID, DialogNudge.SubheadPosition.BOTTOM,
                info.getTitle(), bodyText);

        if (subText != null) {
            nudge.setSubhead(subText.toString());
        }

        if (logo != null) {
            nudge.setTitleImage(logo);
        }

        Intent enableIntent = buildEnableIntent(context, component, info, metricsReason);
        PendingIntent positivePendingIntent = PendingIntent.getService(context,
                REQUEST_CODE_SUCCESS, enableIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        Intent cancelIntent = (Intent) enableIntent.clone();
        cancelIntent.putExtra(ProviderActivationService.INTENT_EXTRA_SUCCESS, false);
        PendingIntent negativePendingIntent = PendingIntent.getService(context,
                REQUEST_CODE_FAILURE, cancelIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        nudge.addButton(new DialogNudge.Button(res.getString(R.string.callerinfo_provider_auth_yes),
                AlertDialog.BUTTON_POSITIVE, positivePendingIntent));
        nudge.addButton(new DialogNudge.Button(res.getString(R.string.callerinfo_provider_auth_no),
                AlertDialog.BUTTON_NEGATIVE, negativePendingIntent));

        DiscoveryManagerServices.DiscoveryManagerApi.
                publishNudge(AmbientConnection.CLIENT.get(context), nudge).
                setResultCallback(callback);
    }

    private static Intent buildEnableIntent(Context context, ComponentName component,
            ProviderInfo info, int metricsReason) {
        Intent intent = new Intent(context, ProviderActivationService.class);
        intent.putExtra(ProviderActivationService.INTENT_EXTRA_PACKAGE, info.getPackageName());
        intent.putExtra(ProviderActivationService.INTENT_EXTRA_COMPONENT, component);
        intent.putExtra(ProviderActivationService.INTENT_EXTRA_METRIC_REASON, metricsReason);
        intent.putExtra(ProviderActivationService.INTENT_EXTRA_SUCCESS, true);
        return intent;
    }
}
+86 −0
Original line number Diff line number Diff line
/*
 * 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.dialer.callerinfo;

import android.app.IntentService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;

import com.android.dialer.util.MetricsHelper;
import com.android.phone.common.ambient.AmbientConnection;
import com.cyanogen.ambient.callerinfo.CallerInfoServices;
import com.cyanogen.ambient.common.api.AmbientApiClient;

public class ProviderActivationService extends IntentService {
    public static final String INTENT_EXTRA_PACKAGE = "package";
    public static final String INTENT_EXTRA_COMPONENT = "component";
    public static final String INTENT_EXTRA_SUCCESS = "success";
    public static final String INTENT_EXTRA_METRIC_REASON = "metric_reason";

    public static final int REASON_FIRST_LAUNCH_DIALER = 0;
    public static final int REASON_INCOMING_CALL_FIRST_PROMPT = 1;
    public static final int REASON_INCOMING_CALL_FINAL_PROMPT = 2;
    public static final int REASON_DIALER_SETTINGS = 3;

    private static final String TAG = ProviderActivationService.class.getSimpleName();

    public ProviderActivationService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(final Intent intent) {
        boolean success = intent.getBooleanExtra(INTENT_EXTRA_SUCCESS, false);
        sendMetrics(intent, success);

        if (success) {
            connectToProvider(getApplicationContext(), intent);
        }
    }

    private static void connectToProvider(final Context context, Intent intent) {
        final ComponentName provider = intent.getParcelableExtra(INTENT_EXTRA_COMPONENT);
        AmbientApiClient client = AmbientConnection.CLIENT.get(context);
        CallerInfoServices.CallerInfoApi.enablePlugin(client, provider);
    }

    private static void sendMetrics(Intent intent, boolean success) {
        int reason = intent.getIntExtra(INTENT_EXTRA_METRIC_REASON, -1);
        MetricsHelper.Field field = new MetricsHelper.Field(
                MetricsHelper.Fields.PROVIDER_PACKAGE_NAME,
                intent.getStringExtra(INTENT_EXTRA_PACKAGE));
        MetricsHelper.Actions action = success ?
                MetricsHelper.Actions.OPTED_IN : MetricsHelper.Actions.OPTED_OUT;
        MetricsHelper.State state = null;
        switch (reason) {
            case REASON_FIRST_LAUNCH_DIALER:
                state = MetricsHelper.State.FIRST_LAUNCH_DIALER;
                break;
            case REASON_INCOMING_CALL_FIRST_PROMPT:
                state = MetricsHelper.State.AFTER_CALL_ENDED;
                break;
            case REASON_INCOMING_CALL_FINAL_PROMPT:
                state = MetricsHelper.State.AFTER_FINAL_PROMPT;
                break;
            case REASON_DIALER_SETTINGS:
                state = MetricsHelper.State.SETTINGS;
                break;
        }

        MetricsHelper.sendEvent(MetricsHelper.Categories.PROVIDER_STATE_CHANGES,
                action, state, field);
    }
}
 No newline at end of file
Loading