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

Commit e85ca463 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Integrate CallDetailsActivity with the new call log UI."

parents ecfba0c3 a98ac7f0
Loading
Loading
Loading
Loading
+98 −9
Original line number Diff line number Diff line
@@ -19,9 +19,12 @@ package com.android.dialer.calldetails;
import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.CallLog;
@@ -35,11 +38,13 @@ import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Toast;
import com.android.dialer.CoalescedIds;
import com.android.dialer.DialerPhoneNumber;
import com.android.dialer.assisteddialing.ui.AssistedDialingSettingActivity;
import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry;
import com.android.dialer.callintent.CallInitiationType;
import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.AsyncTaskExecutors;
@@ -62,6 +67,7 @@ import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil;
import com.android.dialer.postcall.PostCall;
import com.android.dialer.precall.PreCall;
import com.android.dialer.protos.ProtoParsers;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import java.lang.ref.WeakReference;
@@ -71,10 +77,12 @@ import java.util.Map;

/** Displays the details of a specific call log entry. */
public class CallDetailsActivity extends AppCompatActivity {
  private static final int CALL_DETAILS_LOADER_ID = 0;

  public static final String EXTRA_PHONE_NUMBER = "phone_number";
  public static final String EXTRA_HAS_ENRICHED_CALL_DATA = "has_enriched_call_data";
  public static final String EXTRA_CALL_DETAILS_ENTRIES = "call_details_entries";
  public static final String EXTRA_COALESCED_CALL_LOG_IDS = "coalesced_call_log_ids";
  public static final String EXTRA_CONTACT = "contact";
  public static final String EXTRA_CAN_REPORT_CALLER_ID = "can_report_caller_id";
  private static final String EXTRA_CAN_SUPPORT_ASSISTED_DIALING = "can_support_assisted_dialing";
@@ -93,23 +101,47 @@ public class CallDetailsActivity extends AppCompatActivity {
  private DialerContact contact;
  private CallDetailsAdapter adapter;

  // This will be present only when the activity is launched from the new call log UI, i.e., a list
  // of coalesced annotated call log IDs is included in the intent.
  private Optional<CoalescedIds> coalescedCallLogIds = Optional.absent();

  public static boolean isLaunchIntent(Intent intent) {
    return intent.getComponent() != null
        && CallDetailsActivity.class.getName().equals(intent.getComponent().getClassName());
  }

  /**
   * Returns an {@link Intent} for launching the {@link CallDetailsActivity} from the old call log
   * UI.
   */
  public static Intent newInstance(
      Context context,
      @NonNull CallDetailsEntries details,
      @NonNull DialerContact contact,
      CallDetailsEntries details,
      DialerContact contact,
      boolean canReportCallerId,
      boolean canSupportAssistedDialing) {
    Assert.isNotNull(details);
    Assert.isNotNull(contact);
    Intent intent = new Intent(context, CallDetailsActivity.class);
    ProtoParsers.put(intent, EXTRA_CONTACT, Assert.isNotNull(contact));
    ProtoParsers.put(intent, EXTRA_CALL_DETAILS_ENTRIES, Assert.isNotNull(details));
    intent.putExtra(EXTRA_CAN_REPORT_CALLER_ID, canReportCallerId);
    intent.putExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, canSupportAssistedDialing);
    return intent;
  }

  /**
   * Returns an {@link Intent} for launching the {@link CallDetailsActivity} from the new call log
   * UI.
   */
  public static Intent newInstance(
      Context context,
      CoalescedIds coalescedAnnotatedCallLogIds,
      DialerContact contact,
      boolean canReportCallerId,
      boolean canSupportAssistedDialing) {
    Intent intent = new Intent(context, CallDetailsActivity.class);
    ProtoParsers.put(intent, EXTRA_CONTACT, contact);
    ProtoParsers.put(intent, EXTRA_CALL_DETAILS_ENTRIES, details);
    ProtoParsers.put(intent, EXTRA_CONTACT, Assert.isNotNull(contact));
    ProtoParsers.put(
        intent, EXTRA_COALESCED_CALL_LOG_IDS, Assert.isNotNull(coalescedAnnotatedCallLogIds));
    intent.putExtra(EXTRA_CAN_REPORT_CALLER_ID, canReportCallerId);
    intent.putExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, canSupportAssistedDialing);
    return intent;
@@ -166,10 +198,30 @@ public class CallDetailsActivity extends AppCompatActivity {
  }

  private void onHandleIntent(Intent intent) {
    boolean hasCallDetailsEntries = intent.hasExtra(EXTRA_CALL_DETAILS_ENTRIES);
    boolean hasCoalescedCallLogIds = intent.hasExtra(EXTRA_COALESCED_CALL_LOG_IDS);
    Assert.checkArgument(
        (hasCallDetailsEntries && !hasCoalescedCallLogIds)
            || (!hasCallDetailsEntries && hasCoalescedCallLogIds),
        "One and only one of EXTRA_CALL_DETAILS_ENTRIES and EXTRA_COALESCED_CALL_LOG_IDS "
            + "can be included in the intent.");

    contact = ProtoParsers.getTrusted(intent, EXTRA_CONTACT, DialerContact.getDefaultInstance());
    if (hasCallDetailsEntries) {
      entries =
          ProtoParsers.getTrusted(
              intent, EXTRA_CALL_DETAILS_ENTRIES, CallDetailsEntries.getDefaultInstance());
    } else {
      entries = CallDetailsEntries.getDefaultInstance();
      coalescedCallLogIds =
          Optional.of(
              ProtoParsers.getTrusted(
                  intent, EXTRA_COALESCED_CALL_LOG_IDS, CoalescedIds.getDefaultInstance()));
      getLoaderManager()
          .initLoader(
              CALL_DETAILS_LOADER_ID, /* args = */ null, new CallDetailsLoaderCallbacks(this));
    }

    adapter =
        new CallDetailsAdapter(
            this /* context */,
@@ -191,6 +243,43 @@ public class CallDetailsActivity extends AppCompatActivity {
    super.onBackPressed();
  }

  /**
   * {@link LoaderCallbacks} for {@link CallDetailsCursorLoader}, which loads call detail entries
   * from {@link AnnotatedCallLog}.
   */
  private static final class CallDetailsLoaderCallbacks implements LoaderCallbacks<Cursor> {
    private final CallDetailsActivity activity;

    CallDetailsLoaderCallbacks(CallDetailsActivity callDetailsActivity) {
      this.activity = callDetailsActivity;
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
      Assert.checkState(activity.coalescedCallLogIds.isPresent());

      return new CallDetailsCursorLoader(activity, activity.coalescedCallLogIds.get());
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
      updateCallDetailsEntries(CallDetailsCursorLoader.toCallDetailsEntries(data));
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
      updateCallDetailsEntries(CallDetailsEntries.getDefaultInstance());
    }

    private void updateCallDetailsEntries(CallDetailsEntries newEntries) {
      activity.entries = newEntries;
      activity.adapter.updateCallDetailsEntries(newEntries.getEntriesList());
      EnrichedCallComponent.get(activity)
          .getEnrichedCallManager()
          .requestAllHistoricalData(activity.contact.getNumber(), newEntries);
    }
  }

  /** Delete specified calls from the call log. */
  private static class DeleteCallsTask extends AsyncTask<Void, Void, Void> {
    // Use a weak reference to hold the Activity so that there is no memory leak.
+3 −1
Original line number Diff line number Diff line
@@ -115,7 +115,9 @@ final class CallDetailsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol

  @Override
  public int getItemCount() {
    return callDetailsEntries.size() + 2; // Header + footer
    return callDetailsEntries.isEmpty()
        ? 0
        : callDetailsEntries.size() + 2; // plus header and footer
  }

  void updateCallDetailsEntries(List<CallDetailsEntry> entries) {
+139 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.dialer.calldetails;

import android.content.Context;
import android.content.CursorLoader;
import android.database.Cursor;
import com.android.dialer.CoalescedIds;
import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
import com.android.dialer.common.Assert;
import com.android.dialer.duo.DuoConstants;

/**
 * A {@link CursorLoader} that loads call detail entries from {@link AnnotatedCallLog} for {@link
 * CallDetailsActivity}.
 */
public final class CallDetailsCursorLoader extends CursorLoader {

  // Columns in AnnotatedCallLog that are needed to build a CallDetailsEntry proto.
  // Be sure to update (1) constants that store indexes of the elements and (2) method
  // toCallDetailsEntry(Cursor) when updating this array.
  public static final String[] COLUMNS_FOR_CALL_DETAILS =
      new String[] {
        AnnotatedCallLog._ID,
        AnnotatedCallLog.CALL_TYPE,
        AnnotatedCallLog.FEATURES,
        AnnotatedCallLog.TIMESTAMP,
        AnnotatedCallLog.DURATION,
        AnnotatedCallLog.DATA_USAGE,
        AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME
      };

  // Indexes for COLUMNS_FOR_CALL_DETAILS
  private static final int ID = 0;
  private static final int CALL_TYPE = 1;
  private static final int FEATURES = 2;
  private static final int TIMESTAMP = 3;
  private static final int DURATION = 4;
  private static final int DATA_USAGE = 5;
  private static final int PHONE_ACCOUNT_COMPONENT_NAME = 6;

  CallDetailsCursorLoader(Context context, CoalescedIds coalescedIds) {
    super(
        context,
        AnnotatedCallLog.CONTENT_URI,
        COLUMNS_FOR_CALL_DETAILS,
        annotatedCallLogIdsSelection(coalescedIds),
        annotatedCallLogIdsSelectionArgs(coalescedIds),
        AnnotatedCallLog.TIMESTAMP + " DESC");
  }

  /**
   * Build a string of the form "COLUMN_NAME IN (?, ?, ..., ?)", where COLUMN_NAME is the name of
   * the ID column in {@link AnnotatedCallLog}.
   *
   * <p>This string will be used as the {@code selection} parameter to initialize the loader.
   */
  private static String annotatedCallLogIdsSelection(CoalescedIds coalescedIds) {
    // First, build a string of question marks ('?') separated by commas (',').
    StringBuilder questionMarks = new StringBuilder();
    for (int i = 0; i < coalescedIds.getCoalescedIdCount(); i++) {
      if (i != 0) {
        questionMarks.append(", ");
      }
      questionMarks.append("?");
    }

    return AnnotatedCallLog._ID + " IN (" + questionMarks + ")";
  }

  /**
   * Returns a string that will be used as the {@code selectionArgs} parameter to initialize the
   * loader.
   */
  private static String[] annotatedCallLogIdsSelectionArgs(CoalescedIds coalescedIds) {
    String[] args = new String[coalescedIds.getCoalescedIdCount()];

    for (int i = 0; i < coalescedIds.getCoalescedIdCount(); i++) {
      args[i] = String.valueOf(coalescedIds.getCoalescedId(i));
    }

    return args;
  }

  /**
   * Creates a new {@link CallDetailsEntries} from the entire data set loaded by this loader.
   *
   * @param cursor A cursor pointing to the data set loaded by this loader. The caller must ensure
   *     the cursor is not null and the data set it points to is not empty.
   * @return A {@link CallDetailsEntries} proto.
   */
  static CallDetailsEntries toCallDetailsEntries(Cursor cursor) {
    Assert.isNotNull(cursor);
    Assert.checkArgument(cursor.moveToFirst());

    CallDetailsEntries.Builder entries = CallDetailsEntries.newBuilder();

    do {
      entries.addEntries(toCallDetailsEntry(cursor));
    } while (cursor.moveToNext());

    return entries.build();
  }

  /** Creates a new {@link CallDetailsEntry} from the provided cursor using its current position. */
  private static CallDetailsEntry toCallDetailsEntry(Cursor cursor) {
    CallDetailsEntry.Builder entry = CallDetailsEntry.newBuilder();
    entry
        .setCallId(cursor.getLong(ID))
        .setCallType(cursor.getInt(CALL_TYPE))
        .setFeatures(cursor.getInt(FEATURES))
        .setDate(cursor.getLong(TIMESTAMP))
        .setDuration(cursor.getLong(DURATION))
        .setDataUsage(cursor.getLong(DATA_USAGE));

    String phoneAccountComponentName = cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME);
    entry.setIsDuoCall(
        DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME
            .flattenToString()
            .equals(phoneAccountComponentName));

    return entry.build();
  }
}
+6 −8
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import android.provider.ContactsContract;
import android.telecom.PhoneAccountHandle;
import android.text.TextUtils;
import com.android.dialer.calldetails.CallDetailsActivity;
import com.android.dialer.calldetails.CallDetailsEntries;
import com.android.dialer.callintent.CallInitiationType;
import com.android.dialer.calllog.model.CoalescedRow;
import com.android.dialer.calllogutils.PhoneAccountUtils;
@@ -65,7 +64,7 @@ final class Modules {

    // TODO(zachh): Revisit if DialerContact is the best thing to pass to CallDetails; could
    // it use a ContactPrimaryActionInfo instead?
    addModuleForAccessingCallDetails(context, createDialerContactFromRow(row), modules);
    addModuleForAccessingCallDetails(context, row, modules);

    return modules;
  }
@@ -182,10 +181,9 @@ final class Modules {
  }

  private static void addModuleForAccessingCallDetails(
      Context context, DialerContact dialerContact, List<ContactActionModule> modules) {
    // TODO(zachh): Load CallDetailsEntries and canReportInaccurateNumber in
    // CallDetailsActivity (see also isPeopleApiSource(sourceType)).
    CallDetailsEntries callDetailsEntries = CallDetailsEntries.getDefaultInstance();
      Context context, CoalescedRow row, List<ContactActionModule> modules) {
    // TODO(zachh): Load canReportInaccurateNumber in CallDetailsActivity
    // (see also isPeopleApiSource(sourceType)).
    boolean canReportInaccurateNumber = false;
    boolean canSupportAssistedDialing = false; // TODO(zachh): Properly set value.

@@ -194,8 +192,8 @@ final class Modules {
            context,
            CallDetailsActivity.newInstance(
                context,
                callDetailsEntries,
                dialerContact,
                row.coalescedIds(),
                createDialerContactFromRow(row),
                canReportInaccurateNumber,
                canSupportAssistedDialing),
            R.string.call_details_menu_label,