summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
authortwyen <twyen@google.com>2018-06-27 14:48:00 -0700
committerCopybara-Service <copybara-piper@google.com>2018-06-27 15:41:55 -0700
commitd83c23c9fe6cc4eb2923d8b8b258c5f577b7eef3 (patch)
tree859e255dc285185cd6846e8d170e01d81456717e /java
parent3d17fe77a1d9e523cf15838330aee9bacb81ad58 (diff)
Request high resolution photo to be downloaded by the sync adapter when a contact is added to the favorites.
To conserve resources synced contacts only have the low-res icon by default, and the hi-res photo is only synced when the contact is viewed. When a contact is "viewed" in dialer, dialer should send a ACTION_VIEW with the contact URI to the sync adapter service. TEST=TAP Test: TAP PiperOrigin-RevId: 202373390 Change-Id: Ie3a173b7c3f442dc806a719910aea9b3a6c5cf4f
Diffstat (limited to 'java')
-rw-r--r--java/com/android/dialer/contacts/ContactsComponent.java5
-rw-r--r--java/com/android/dialer/contacts/ContactsModule.java9
-rw-r--r--java/com/android/dialer/contacts/hiresphoto/HighResolutionPhotoRequester.java30
-rw-r--r--java/com/android/dialer/contacts/hiresphoto/HighResolutionPhotoRequesterImpl.java137
-rw-r--r--java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java25
5 files changed, 204 insertions, 2 deletions
diff --git a/java/com/android/dialer/contacts/ContactsComponent.java b/java/com/android/dialer/contacts/ContactsComponent.java
index 5c4097ace..9c6773716 100644
--- a/java/com/android/dialer/contacts/ContactsComponent.java
+++ b/java/com/android/dialer/contacts/ContactsComponent.java
@@ -18,7 +18,9 @@ package com.android.dialer.contacts;
import android.content.Context;
import com.android.dialer.contacts.displaypreference.ContactDisplayPreferences;
+import com.android.dialer.contacts.hiresphoto.HighResolutionPhotoRequester;
import com.android.dialer.inject.HasRootComponent;
+import com.android.dialer.inject.IncludeInDialerRoot;
import dagger.Subcomponent;
/** Component for contacts related utilities */
@@ -27,12 +29,15 @@ public abstract class ContactsComponent {
public abstract ContactDisplayPreferences contactDisplayPreferences();
+ public abstract HighResolutionPhotoRequester highResolutionPhotoLoader();
+
public static ContactsComponent get(Context context) {
return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component())
.contactsComponent();
}
/** Used to refer to the root application component. */
+ @IncludeInDialerRoot
public interface HasComponent {
ContactsComponent contactsComponent();
}
diff --git a/java/com/android/dialer/contacts/ContactsModule.java b/java/com/android/dialer/contacts/ContactsModule.java
index 979c525eb..73731e544 100644
--- a/java/com/android/dialer/contacts/ContactsModule.java
+++ b/java/com/android/dialer/contacts/ContactsModule.java
@@ -18,6 +18,8 @@ package com.android.dialer.contacts;
import com.android.dialer.contacts.displaypreference.ContactDisplayPreferences;
import com.android.dialer.contacts.displaypreference.ContactDisplayPreferencesImpl;
+import com.android.dialer.contacts.hiresphoto.HighResolutionPhotoRequester;
+import com.android.dialer.contacts.hiresphoto.HighResolutionPhotoRequesterImpl;
import com.android.dialer.inject.DialerVariant;
import com.android.dialer.inject.InstallIn;
import dagger.Binds;
@@ -28,5 +30,10 @@ import dagger.Module;
@Module
public abstract class ContactsModule {
@Binds
- public abstract ContactDisplayPreferences to(ContactDisplayPreferencesImpl impl);
+ public abstract ContactDisplayPreferences toContactDisplayPreferencesImpl(
+ ContactDisplayPreferencesImpl impl);
+
+ @Binds
+ public abstract HighResolutionPhotoRequester toHighResolutionPhotoRequesterImpl(
+ HighResolutionPhotoRequesterImpl impl);
}
diff --git a/java/com/android/dialer/contacts/hiresphoto/HighResolutionPhotoRequester.java b/java/com/android/dialer/contacts/hiresphoto/HighResolutionPhotoRequester.java
new file mode 100644
index 000000000..1075ec171
--- /dev/null
+++ b/java/com/android/dialer/contacts/hiresphoto/HighResolutionPhotoRequester.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 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.contacts.hiresphoto;
+
+import android.net.Uri;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Requests the contacts sync adapter to load a high resolution photo for the contact, typically
+ * when we will try to show the contact in a larger view (favorites, incall UI, etc.). If a high
+ * resolution photo is synced, the uri will be notified.
+ */
+public interface HighResolutionPhotoRequester {
+
+ ListenableFuture<Void> request(Uri contactUri);
+}
diff --git a/java/com/android/dialer/contacts/hiresphoto/HighResolutionPhotoRequesterImpl.java b/java/com/android/dialer/contacts/hiresphoto/HighResolutionPhotoRequesterImpl.java
new file mode 100644
index 000000000..9201604be
--- /dev/null
+++ b/java/com/android/dialer/contacts/hiresphoto/HighResolutionPhotoRequesterImpl.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2018 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.contacts.hiresphoto;
+
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.RawContacts;
+import android.support.annotation.VisibleForTesting;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
+import com.android.dialer.common.database.Selection;
+import com.android.dialer.inject.ApplicationContext;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import java.util.ArrayList;
+import java.util.List;
+import javax.inject.Inject;
+
+/** Use the contacts sync adapter to load high resolution photos for a Google account. */
+public class HighResolutionPhotoRequesterImpl implements HighResolutionPhotoRequester {
+
+ private static class RequestFailedException extends Exception {
+ RequestFailedException(String message) {
+ super(message);
+ }
+
+ RequestFailedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ @VisibleForTesting
+ static final ComponentName SYNC_HIGH_RESOLUTION_PHOTO_SERVICE =
+ new ComponentName(
+ "com.google.android.syncadapters.contacts",
+ "com.google.android.syncadapters.contacts.SyncHighResPhotoIntentService");
+
+ private final Context appContext;
+ private final ListeningExecutorService backgroundExecutor;
+
+ @Inject
+ HighResolutionPhotoRequesterImpl(
+ @ApplicationContext Context appContext,
+ @BackgroundExecutor ListeningExecutorService backgroundExecutor) {
+ this.appContext = appContext;
+ this.backgroundExecutor = backgroundExecutor;
+ }
+
+ @Override
+ public ListenableFuture<Void> request(Uri contactUri) {
+ return backgroundExecutor.submit(
+ () -> {
+ try {
+ requestInternal(contactUri);
+ } catch (RequestFailedException e) {
+ LogUtil.e("HighResolutionPhotoRequesterImpl.request", "request failed", e);
+ }
+ return null;
+ });
+ }
+
+ private void requestInternal(Uri contactUri) throws RequestFailedException {
+ for (Long rawContactId : getGoogleRawContactIds(getContactId(contactUri))) {
+ Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setComponent(SYNC_HIGH_RESOLUTION_PHOTO_SERVICE);
+ intent.setDataAndType(rawContactUri, RawContacts.CONTENT_ITEM_TYPE);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ try {
+ LogUtil.i(
+ "HighResolutionPhotoRequesterImpl.requestInternal",
+ "requesting photo for " + rawContactUri);
+ appContext.startService(intent);
+ } catch (IllegalStateException | SecurityException e) {
+ throw new RequestFailedException("unable to start sync adapter", e);
+ }
+ }
+ }
+
+ private long getContactId(Uri contactUri) throws RequestFailedException {
+ try (Cursor cursor =
+ appContext
+ .getContentResolver()
+ .query(contactUri, new String[] {Contacts._ID}, null, null, null)) {
+ if (cursor == null || !cursor.moveToFirst()) {
+ throw new RequestFailedException("cannot get contact ID");
+ }
+ return cursor.getLong(0);
+ }
+ }
+
+ private List<Long> getGoogleRawContactIds(long contactId) throws RequestFailedException {
+ List<Long> result = new ArrayList<>();
+ Selection selection =
+ Selection.column(RawContacts.CONTACT_ID)
+ .is("=", contactId)
+ .buildUpon()
+ .and(Selection.column(RawContacts.ACCOUNT_TYPE).is("=", "com.google"))
+ .build();
+ try (Cursor cursor =
+ appContext
+ .getContentResolver()
+ .query(
+ RawContacts.CONTENT_URI,
+ new String[] {RawContacts._ID, RawContacts.ACCOUNT_TYPE},
+ selection.getSelection(),
+ selection.getSelectionArgs(),
+ null)) {
+ if (cursor == null) {
+ throw new RequestFailedException("null cursor from raw contact IDs");
+ }
+ for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
+ result.add(cursor.getLong(0));
+ }
+ }
+ return result;
+ }
+}
diff --git a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java
index b0b83ac32..86d5d37a9 100644
--- a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java
+++ b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java
@@ -34,11 +34,14 @@ import android.util.ArraySet;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
+import com.android.dialer.common.concurrent.DefaultFutureCallback;
import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener;
import com.android.dialer.common.concurrent.DialerFutureSerializer;
import com.android.dialer.common.database.Selection;
+import com.android.dialer.contacts.ContactsComponent;
import com.android.dialer.contacts.displaypreference.ContactDisplayPreferences;
import com.android.dialer.contacts.displaypreference.ContactDisplayPreferences.DisplayOrder;
+import com.android.dialer.contacts.hiresphoto.HighResolutionPhotoRequester;
import com.android.dialer.duo.DuoComponent;
import com.android.dialer.inject.ApplicationContext;
import com.android.dialer.speeddial.database.SpeedDialEntry;
@@ -49,8 +52,10 @@ import com.android.dialer.util.CallUtil;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -86,15 +91,18 @@ public final class SpeedDialUiItemMutator {
// Used to ensure that only one refresh flow runs at a time.
private final DialerFutureSerializer dialerFutureSerializer = new DialerFutureSerializer();
private final ContactDisplayPreferences contactDisplayPreferences;
+ private final HighResolutionPhotoRequester highResolutionPhotoRequester;
@Inject
public SpeedDialUiItemMutator(
@ApplicationContext Context appContext,
@BackgroundExecutor ListeningExecutorService backgroundExecutor,
- ContactDisplayPreferences contactDisplayPreferences) {
+ ContactDisplayPreferences contactDisplayPreferences,
+ HighResolutionPhotoRequester highResolutionPhotoRequester) {
this.appContext = appContext;
this.backgroundExecutor = backgroundExecutor;
this.contactDisplayPreferences = contactDisplayPreferences;
+ this.highResolutionPhotoRequester = highResolutionPhotoRequester;
}
/**
@@ -287,6 +295,7 @@ public final class SpeedDialUiItemMutator {
Trace.endSection(); // addStarredContact
Trace.beginSection("insertUpdateAndDelete");
+ requestHighResolutionPhoto(entriesToInsert);
ImmutableMap<SpeedDialEntry, Long> insertedEntriesToIdsMap =
db.insertUpdateAndDelete(
ImmutableList.copyOf(entriesToInsert),
@@ -297,6 +306,20 @@ public final class SpeedDialUiItemMutator {
return speedDialUiItemsWithUpdatedIds(speedDialUiItems, insertedEntriesToIdsMap);
}
+ @WorkerThread
+ private void requestHighResolutionPhoto(List<SpeedDialEntry> newEntries) {
+ ContactsComponent.get(appContext).highResolutionPhotoLoader();
+ for (SpeedDialEntry entry : newEntries) {
+ Uri uri;
+ uri = Contacts.getLookupUri(entry.contactId(), entry.lookupKey());
+
+ Futures.addCallback(
+ highResolutionPhotoRequester.request(uri),
+ new DefaultFutureCallback<>(),
+ MoreExecutors.directExecutor());
+ }
+ }
+
/**
* Since newly starred contacts sometimes aren't in the SpeedDialEntry database, we couldn't set
* their ids when we created our initial list of {@link SpeedDialUiItem speedDialUiItems}. Now