Merge "Change log behavior to log conference calls only when they are disconnected from conference."
diff --git a/src/com/android/server/telecom/NuisanceCallReporter.java b/src/com/android/server/telecom/NuisanceCallReporter.java
new file mode 100644
index 0000000..064527b
--- /dev/null
+++ b/src/com/android/server/telecom/NuisanceCallReporter.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2019 The Android Open Source 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.server.telecom;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Process;
+import android.os.UserHandle;
+import android.provider.CallLog;
+import android.telecom.CallScreeningService;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+
+import java.util.Arrays;
+
+/**
+ * Responsible for handling reports via
+ * {@link android.telecom.TelecomManager#reportNuisanceCallStatus(Uri, boolean)} as to whether the
+ * user has indicated a call is a nuisance call.
+ *
+ * Since nuisance reports can be initiated from the call log, potentially long after a call has
+ * completed, calls are identified by the {@link Call#getHandle()}.  A nuisance report for a call is
+ * only accepted if:
+ * <ul>
+ *     <li>A missed, incoming, or rejected call to that number exists in the call log.  We want to
+ *     avoid a scenario where a user reports a single outgoing call as a nuisance call.</li>
+ *     <li>The call occurred via a sim-based phone account; we do not want to report nuisance calls
+ *     which only ever took place via a self-managed ConnectionService.  It is, however, valid for
+ *     a number to be contacted both via a sim-based phone account and a self-managed one.</li>
+ *     <li>The {@link CallScreeningService} has provided call identification for calls in the past.
+ *     This provides an incentive for {@link CallScreeningService} implementations to use the caller
+ *     ID APIs appropriately if they are going to benefit from use reports of nuisance and
+ *     non-nuisance calls.</li>
+ * </ul>
+ */
+public class NuisanceCallReporter {
+    /**
+     * Columns we want to retrieve from the call log.
+     */
+    private static final String[] CALL_LOG_PROJECTION = new String[] {
+            CallLog.Calls._ID,
+            CallLog.Calls.DURATION,
+            CallLog.Calls.TYPE,
+            CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME,
+            CallLog.Calls.PHONE_ACCOUNT_ID
+    };
+
+    public static final int CALL_LOG_COLUMN_ID =
+            Arrays.asList(CALL_LOG_PROJECTION).indexOf(CallLog.Calls._ID);
+    public static final int CALL_LOG_COLUMN_DURATION =
+            Arrays.asList(CALL_LOG_PROJECTION).indexOf(CallLog.Calls.DURATION);
+    public static final int CALL_LOG_COLUMN_TYPE =
+            Arrays.asList(CALL_LOG_PROJECTION).indexOf(CallLog.Calls.TYPE);
+    public static final int CALL_LOG_COLUMN_PHONE_ACCOUNT_COMPONENT_NAME =
+            Arrays.asList(CALL_LOG_PROJECTION).indexOf(CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME);
+    public static final int CALL_LOG_COLUMN_PHONE_ACCOUNT_ID =
+            Arrays.asList(CALL_LOG_PROJECTION).indexOf(CallLog.Calls.PHONE_ACCOUNT_ID);
+
+    /**
+     * Represents information about a nuisance report via
+     * {@link android.telecom.TelecomManager#reportNuisanceCallStatus(Uri, boolean)}.
+     */
+    private static class NuisanceReport {
+        public String callScreeningPackageName;
+        public Uri handle;
+        public boolean isNuisance;
+        public NuisanceReport(String packageName, Uri handle, boolean isNuisance) {
+            this.callScreeningPackageName = packageName;
+            this.handle = handle;
+            this.isNuisance = isNuisance;
+        }
+    }
+
+    /**
+     * Proxy interface to abstract calls to
+     * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}.
+     * Facilitates testing.
+     */
+    public interface PhoneNumberUtilsProxy {
+        String formatNumberToE164(String number);
+    }
+
+    /**
+     * Proxy interface to abstract queries to the package manager to determine if a
+     * {@link PhoneAccountHandle} is for a self-managed connection service.
+     */
+    public interface PhoneAccountRegistrarProxy {
+        boolean isSelfManagedConnectionService(PhoneAccountHandle handle);
+    }
+
+    /**
+     * Restrict to call log entries for the specified number where its an incoming, missed, blocked
+     * or rejected call.
+     */
+    private static final String NUMBER_WHERE_CLAUSE =
+            CallLog.Calls.CACHED_NORMALIZED_NUMBER + " = ? AND " + CallLog.Calls.TYPE
+                    + " IN (" + CallLog.Calls.INCOMING_TYPE + "," + CallLog.Calls.MISSED_TYPE + ","
+                    + CallLog.Calls.BLOCKED_TYPE + "," + CallLog.Calls.REJECTED_TYPE + ")";
+
+    /**
+     * Call log where clause to find entries with call identification reported by a specified call
+     * screening service.
+     */
+    private static final String CALL_ID_PACKAGE_WHERE_CLAUSE =
+            CallLog.Calls.CALL_ID_PACKAGE_NAME + " = ? ";
+
+    private final Context mContext;
+    private final PhoneNumberUtilsProxy mPhoneNumberUtilsProxy;
+    private final PhoneAccountRegistrarProxy mPhoneAccountRegistrarProxy;
+    private UserHandle mCurrentUserHandle;
+
+    public NuisanceCallReporter(Context context, PhoneNumberUtilsProxy phoneNumberUtilsProxy,
+            PhoneAccountRegistrarProxy phoneAccountRegistrarProxy) {
+        mContext = context;
+        mPhoneNumberUtilsProxy = phoneNumberUtilsProxy;
+        mPhoneAccountRegistrarProxy = phoneAccountRegistrarProxy;
+    }
+
+    public void setCurrentUserHandle(UserHandle userHandle) {
+        if (userHandle == null) {
+            Log.d(this, "setCurrentUserHandle, userHandle = null");
+            userHandle = Process.myUserHandle();
+        }
+        Log.d(this, "setCurrentUserHandle, %s", userHandle);
+        mCurrentUserHandle = userHandle;
+    }
+
+    /**
+     * Given a call handle reported by the default dialer, inform the
+     * {@link android.telecom.CallScreeningService} of whether the user has indicated a call is
+     * or is not a nuisance call.
+     *
+     * @param callScreeningPackageName the package name of the call screening service.
+     * @param handle the handle of the call to report nuisance status on.
+     * @param isNuisance {@code true} if the call is a nuisance call, {@code false} otherwise.
+     */
+    public void reportNuisanceCallStatus(@NonNull String callScreeningPackageName,
+            @NonNull Uri handle, boolean isNuisance) {
+
+        // Don't report the nuisance status to a call screening app if it has not provided any
+        // caller id info in the past.
+        if (!hasCallScreeningServiceProvidedCallId(callScreeningPackageName)) {
+            Log.i(this, "reportNuisanceCallStatus: app %s has not provided caller ID; skipping.",
+                    callScreeningPackageName);
+            return;
+        }
+
+        maybeSendNuisanceReport(new NuisanceReport(callScreeningPackageName, handle, isNuisance));
+    }
+
+    private void maybeSendNuisanceReport(@NonNull NuisanceReport nuisanceReport) {
+        Uri callsUri = CallLog.Calls.CONTENT_URI;
+        if (mCurrentUserHandle == null) {
+            return;
+        }
+
+        ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI,
+                mCurrentUserHandle.getIdentifier());
+
+        String normalizedNumber = mPhoneNumberUtilsProxy.formatNumberToE164(
+                nuisanceReport.handle.getSchemeSpecificPart());
+
+        // Query the call log for the most recent information about this call.
+        Cursor cursor = mContext.getContentResolver().query(callsUri, CALL_LOG_PROJECTION,
+                NUMBER_WHERE_CLAUSE, new String[] { normalizedNumber },
+                CallLog.Calls.DEFAULT_SORT_ORDER);
+        Log.d(this, "maybeSendNuisanceReport:  number=%s, isNuisance=%b",
+                Log.piiHandle(normalizedNumber), nuisanceReport.isNuisance);
+        if (cursor != null) {
+            try {
+                while (cursor.moveToNext()) {
+                    final long duration = cursor.getLong(CALL_LOG_COLUMN_DURATION);
+                    final int callType = cursor.getInt(CALL_LOG_COLUMN_TYPE);
+                    final String phoneAccountComponentName = cursor.getString(
+                            CALL_LOG_COLUMN_PHONE_ACCOUNT_COMPONENT_NAME);
+                    final String phoneAccountId = cursor.getString(
+                            CALL_LOG_COLUMN_PHONE_ACCOUNT_ID);
+
+                    PhoneAccountHandle handle = new PhoneAccountHandle(
+                            ComponentName.unflattenFromString(phoneAccountComponentName),
+                            phoneAccountId);
+
+                    if (mPhoneAccountRegistrarProxy.isSelfManagedConnectionService(handle)) {
+                        // Skip this call log entry; it was made via a self-managed CS.
+                        Log.d(this, "maybeSendNuisanceReport: skip self-mgd CS %s",
+                                phoneAccountComponentName);
+                        continue;
+                    }
+
+                    sendNuisanceReportIntent(nuisanceReport, duration, callType);
+                    // Stop when we send a nuisance report.
+                    break;
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+    }
+
+    /**
+     * Determines if a {@link CallScreeningService} has provided
+     * {@link android.telecom.CallIdentification} for calls in the past.
+     * @param packageName The package name of the {@link CallScreeningService}.
+     * @return {@code true} if the app has provided call identification, {@code false} otherwise.
+     */
+    private boolean hasCallScreeningServiceProvidedCallId(@NonNull String packageName) {
+        // Query the call log for any entries which have call identification provided by the call
+        // screening package.
+        Cursor cursor = mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI,
+                CALL_LOG_PROJECTION, CALL_ID_PACKAGE_WHERE_CLAUSE, new String[] { packageName },
+                CallLog.Calls.DEFAULT_SORT_ORDER + " LIMIT 1");
+
+        return cursor.getCount() > 0;
+    }
+
+    private void sendNuisanceReportIntent(@NonNull NuisanceReport nuisanceReport, long duration,
+            int callType) {
+        Log.i(this, "handleCallLogResult: number=%s, duration=%d, type=%d",
+                Log.piiHandle(nuisanceReport.handle), duration, callType);
+
+        Intent intent = new Intent(CallScreeningService.ACTION_NUISANCE_CALL_STATUS_CHANGED);
+        intent.setPackage(nuisanceReport.callScreeningPackageName);
+        intent.putExtra(CallScreeningService.EXTRA_CALL_HANDLE, nuisanceReport.handle);
+        intent.putExtra(CallScreeningService.EXTRA_IS_NUISANCE, nuisanceReport.isNuisance);
+        intent.putExtra(CallScreeningService.EXTRA_CALL_TYPE, callType);
+        intent.putExtra(CallScreeningService.EXTRA_CALL_DURATION, getCallDurationBucket(duration));
+        mContext.sendBroadcastAsUser(intent, mCurrentUserHandle);
+    }
+
+    /**
+     * Maps a call duration in milliseconds to a call duration bucket.
+     * @param callDuration Call duration, in milliseconds.
+     * @return The call duration bucket
+     */
+    public @CallScreeningService.CallDuration int getCallDurationBucket(long callDuration) {
+        if (callDuration < 3000L) {
+            return CallScreeningService.CALL_DURATION_VERY_SHORT;
+        } else if (callDuration >= 3000L && callDuration < 60000L) {
+            return CallScreeningService.CALL_DURATION_SHORT;
+        } else if (callDuration >= 6000L && callDuration < 120000L) {
+            return CallScreeningService.CALL_DURATION_MEDIUM;
+        } else {
+            return CallScreeningService.CALL_DURATION_LONG;
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 9010913..afe5609 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -650,6 +651,21 @@
         return getPhoneAccountHandles(0, null, packageName, false, userHandle);
     }
 
+    /**
+     * Determines if a {@link PhoneAccountHandle} is for a self-managed {@link ConnectionService}.
+     * @param handle The handle.
+     * @return {@code true} if for a self-managed {@link ConnectionService}, {@code false}
+     * otherwise.
+     */
+    public boolean isSelfManagedPhoneAccount(@NonNull PhoneAccountHandle handle) {
+        PhoneAccount account = getPhoneAccountUnchecked(handle);
+        if (account == null) {
+            return false;
+        }
+
+        return account.isSelfManaged();
+    }
+
     // TODO: Should we implement an artificial limit for # of accounts associated with a single
     // ComponentName?
     public void registerPhoneAccount(PhoneAccount account) {
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index f472421..291f9d0 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -1506,6 +1506,36 @@
         }
 
         /**
+         * See {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}
+         */
+        @Override
+        public void reportNuisanceCallStatus(Uri handle, boolean isNuisance,
+                String callingPackage) {
+            try {
+                Log.startSession("TSI.rNCS");
+                if (!isPrivilegedDialerCalling(callingPackage)) {
+                    throw new SecurityException(
+                            "Only the default dialer can report nuisance call status");
+                }
+
+                long token = Binder.clearCallingIdentity();
+                try {
+                    String callScreeningPackageName =
+                            mCallsManager.getRoleManagerAdapter().getDefaultCallScreeningApp();
+
+                    if (!TextUtils.isEmpty(callScreeningPackageName)) {
+                        mNuisanceCallReporter.reportNuisanceCallStatus(callScreeningPackageName,
+                                handle, isNuisance);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        /**
          * See {@link TelecomManager#handleCallIntent(Intent)} ()}
          */
         @Override
@@ -1678,6 +1708,7 @@
     private AppOpsManager mAppOpsManager;
     private PackageManager mPackageManager;
     private CallsManager mCallsManager;
+    private final NuisanceCallReporter mNuisanceCallReporter;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final CallIntentProcessor.Adapter mCallIntentProcessorAdapter;
     private final UserCallIntentProcessorFactory mUserCallIntentProcessorFactory;
@@ -1695,6 +1726,7 @@
             DefaultDialerCache defaultDialerCache,
             SubscriptionManagerAdapter subscriptionManagerAdapter,
             SettingsSecureAdapter settingsSecureAdapter,
+            NuisanceCallReporter nuisanceCallReporter,
             TelecomSystem.SyncRoot lock) {
         mContext = context;
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -1709,6 +1741,7 @@
         mCallIntentProcessorAdapter = callIntentProcessorAdapter;
         mSubscriptionManagerAdapter = subscriptionManagerAdapter;
         mSettingsSecureAdapter = settingsSecureAdapter;
+        mNuisanceCallReporter = nuisanceCallReporter;
     }
 
     public ITelecomService.Stub getBinder() {
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 2daba61..0e3234e 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -35,10 +35,15 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.location.Country;
+import android.location.CountryDetector;
 import android.net.Uri;
+import android.os.Process;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
+import android.telephony.PhoneNumberUtils;
 
 import java.io.FileNotFoundException;
 import java.io.InputStream;
@@ -115,6 +120,7 @@
     private final TelecomServiceImpl mTelecomServiceImpl;
     private final ContactsAsyncHelper mContactsAsyncHelper;
     private final DialerCodeReceiver mDialerCodeReceiver;
+    private final NuisanceCallReporter mNuisanceCallReporter;
 
     private boolean mIsBootComplete = false;
 
@@ -128,6 +134,7 @@
                     UserHandle currentUserHandle = new UserHandle(userHandleId);
                     mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
                     mCallsManager.onUserSwitch(currentUserHandle);
+                    mNuisanceCallReporter.setCurrentUserHandle(currentUserHandle);
                 }
             } finally {
                 Log.endSession();
@@ -326,6 +333,41 @@
         mContext.registerReceiver(mDialerCodeReceiver, DIALER_SECRET_CODE_FILTER,
                 Manifest.permission.CONTROL_INCALL_EXPERIENCE, null);
 
+        mNuisanceCallReporter = new NuisanceCallReporter(mContext,
+                new NuisanceCallReporter.PhoneNumberUtilsProxy() {
+                    @Override
+                    public String formatNumberToE164(String number) {
+                        final CountryDetector detector =
+                                (CountryDetector) context.getSystemService(
+                                        Context.COUNTRY_DETECTOR);
+                        if (detector != null) {
+                            final Country country = detector.detectCountry();
+                            if (country != null) {
+                                String countryIso = country.getCountryIso();
+                                return PhoneNumberUtils.formatNumberToE164(number,
+                                        countryIso);
+                            }
+                        }
+                        return number;
+                    }
+                },
+                new NuisanceCallReporter.PhoneAccountRegistrarProxy() {
+                    @Override
+                    public boolean isSelfManagedConnectionService(
+                            PhoneAccountHandle handle) {
+                        // Sync access to the PhoneAccountRegistrar on Telecom lock.
+                        synchronized (mLock) {
+                            return mPhoneAccountRegistrar.isSelfManagedPhoneAccount(handle);
+                        }
+                    }
+                });
+
+        // There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
+        final UserManager userManager = UserManager.get(mContext);
+        if (userManager.isPrimaryUser()) {
+            mNuisanceCallReporter.setCurrentUserHandle(Process.myUserHandle());
+        }
+
         mTelecomServiceImpl = new TelecomServiceImpl(
                 mContext, mCallsManager, mPhoneAccountRegistrar,
                 new CallIntentProcessor.AdapterImpl(),
@@ -338,6 +380,7 @@
                 defaultDialerCache,
                 new TelecomServiceImpl.SubscriptionManagerAdapterImpl(),
                 new TelecomServiceImpl.SettingsSecureAdapterImpl(),
+                mNuisanceCallReporter,
                 mLock);
         Log.endSession();
     }
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 9cb1992..1373906 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -239,6 +239,13 @@
             android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
             android:name="com.android.server.telecom.testapps.SelfManagedCallNotificationReceiver" />
 
+        <receiver android:exported="true"
+                  android:name="com.android.server.telecom.testapps.NuisanceReportReceiver">
+            <intent-filter>
+                <action android:name="android.telecom.action.NUISANCE_CALL_STATUS_CHANGED" />
+            </intent-filter>
+        </receiver>
+
         <service
             android:name=".TestCallScreeningService"
             android:permission="android.permission.BIND_SCREENING_SERVICE">
diff --git a/testapps/res/layout/testdialer_main.xml b/testapps/res/layout/testdialer_main.xml
index e6c56b7..9da3789 100644
--- a/testapps/res/layout/testdialer_main.xml
+++ b/testapps/res/layout/testdialer_main.xml
@@ -30,6 +30,16 @@
         android:layout_height="wrap_content"
         android:text="@string/placeCallButton" />
     <Button
+        android:id="@+id/report_nuisance_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Report Nuisance" />
+    <Button
+        android:id="@+id/report_not_nuisance_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Report Not Nuisance" />
+    <Button
         android:id="@+id/set_default_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/testapps/src/com/android/server/telecom/testapps/NuisanceReportReceiver.java b/testapps/src/com/android/server/telecom/testapps/NuisanceReportReceiver.java
new file mode 100644
index 0000000..76fff66
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/NuisanceReportReceiver.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 The Android Open Source 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.server.telecom.testapps;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.telecom.CallScreeningService;
+import android.widget.Toast;
+
+/**
+ * Example receiver for nuisance call reports from Telecom.
+ */
+public class NuisanceReportReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction().equals(CallScreeningService.ACTION_NUISANCE_CALL_STATUS_CHANGED)) {
+            Uri handle = intent.getParcelableExtra(CallScreeningService.EXTRA_CALL_HANDLE);
+            boolean isNuisance = intent.getBooleanExtra(CallScreeningService.EXTRA_IS_NUISANCE,
+                    false);
+            int durationBucket = intent.getIntExtra(CallScreeningService.EXTRA_CALL_DURATION, 0);
+            int callType = intent.getIntExtra(CallScreeningService.EXTRA_CALL_TYPE, 0);
+            handleNuisanceReport(context, handle, isNuisance, durationBucket, callType);
+        }
+    }
+
+    private void handleNuisanceReport(Context context, Uri handle, boolean isNuisance,
+            int durationBucket, int callType) {
+
+        String message = "Nuisance report for: " + handle + " isNuisance=" + isNuisance
+                + " duration=" + durationBucket + " callType=" + callType;
+        Toast.makeText(context,
+                (CharSequence) message,
+                Toast.LENGTH_LONG)
+                .show();
+    }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
index c7eccf7..66a6944 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
@@ -54,6 +54,20 @@
             }
         });
 
+        findViewById(R.id.report_nuisance_button).setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                reportNuisance(true);
+            }
+        });
+
+        findViewById(R.id.report_not_nuisance_button).setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                reportNuisance(false);
+            }
+        });
+
         mNumberView = (EditText) findViewById(R.id.number);
         mRttCheckbox = (CheckBox) findViewById(R.id.call_with_rtt_checkbox);
         updateMutableUi();
@@ -140,4 +154,11 @@
         Log.i("Santos xtr", intentExtras.toString());
         return intentExtras;
     }
+
+    private void reportNuisance(boolean isNuisance) {
+        final TelecomManager telecomManager =
+                (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
+        telecomManager.reportNuisanceCallStatus(Uri.fromParts(PhoneAccount.SCHEME_TEL,
+                mNumberView.getText().toString(), null), isNuisance);
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index b779ce2..512dbe0 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -51,6 +51,7 @@
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.NuisanceCallReporter;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.TelecomServiceImpl;
 import com.android.server.telecom.TelecomSystem;
@@ -166,6 +167,7 @@
     private TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter =
         spy(new SettingsSecureAdapterFake());
     @Mock private UserCallIntentProcessor mUserCallIntentProcessor;
+    @Mock private NuisanceCallReporter mNuisanceCallReporter;
 
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
@@ -208,6 +210,7 @@
                 mDefaultDialerCache,
                 mSubscriptionManagerAdapter,
                 mSettingsSecureAdapter,
+                mNuisanceCallReporter,
                 mLock);
         mTSIBinder = telecomServiceImpl.getBinder();
         mComponentContextFixture.setTelecomManager(mTelecomManager);