1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.dialer.contacts.hiresphoto;
18 
19 import android.content.ComponentName;
20 import android.content.ContentUris;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.database.Cursor;
24 import android.net.Uri;
25 import android.provider.ContactsContract.Contacts;
26 import android.provider.ContactsContract.RawContacts;
27 import android.support.annotation.VisibleForTesting;
28 import com.android.dialer.common.LogUtil;
29 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
30 import com.android.dialer.common.database.Selection;
31 import com.android.dialer.inject.ApplicationContext;
32 import com.google.common.util.concurrent.ListenableFuture;
33 import com.google.common.util.concurrent.ListeningExecutorService;
34 import java.util.ArrayList;
35 import java.util.List;
36 import javax.inject.Inject;
37 
38 /** Use the contacts sync adapter to load high resolution photos for a Google account. */
39 public class HighResolutionPhotoRequesterImpl implements HighResolutionPhotoRequester {
40 
41   private static class RequestFailedException extends Exception {
RequestFailedException(String message)42     RequestFailedException(String message) {
43       super(message);
44     }
45 
RequestFailedException(String message, Throwable cause)46     RequestFailedException(String message, Throwable cause) {
47       super(message, cause);
48     }
49   }
50 
51   @VisibleForTesting
52   static final ComponentName SYNC_HIGH_RESOLUTION_PHOTO_SERVICE =
53       new ComponentName(
54           "com.google.android.gms",
55           "com.google.android.gms.people.sync.focus.SyncHighResPhotoIntentOperation");
56 
57   private final Context appContext;
58   private final ListeningExecutorService backgroundExecutor;
59 
60   @Inject
HighResolutionPhotoRequesterImpl( @pplicationContext Context appContext, @BackgroundExecutor ListeningExecutorService backgroundExecutor)61   HighResolutionPhotoRequesterImpl(
62       @ApplicationContext Context appContext,
63       @BackgroundExecutor ListeningExecutorService backgroundExecutor) {
64     this.appContext = appContext;
65     this.backgroundExecutor = backgroundExecutor;
66   }
67 
68   @Override
request(Uri contactUri)69   public ListenableFuture<Void> request(Uri contactUri) {
70     return backgroundExecutor.submit(
71         () -> {
72           try {
73             requestInternal(contactUri);
74           } catch (RequestFailedException e) {
75             LogUtil.e("HighResolutionPhotoRequesterImpl.request", "request failed", e);
76           }
77           return null;
78         });
79   }
80 
81   private void requestInternal(Uri contactUri) throws RequestFailedException {
82     for (Long rawContactId : getGoogleRawContactIds(getContactId(contactUri))) {
83       Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
84       Intent intent = new Intent();
85       intent.setAction("com.google.android.gms.people.sync.focus.SYNC_HIGH_RES_PHOTO");
86       intent.setComponent(SYNC_HIGH_RESOLUTION_PHOTO_SERVICE);
87       intent.setDataAndType(rawContactUri, RawContacts.CONTENT_ITEM_TYPE);
88       intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
89       try {
90         LogUtil.i(
91             "HighResolutionPhotoRequesterImpl.requestInternal",
92             "requesting photo for " + rawContactUri);
93         appContext.sendBroadcast(intent);
94       } catch (IllegalStateException | SecurityException e) {
95         throw new RequestFailedException("unable to start sync adapter", e);
96       }
97     }
98   }
99 
100   private long getContactId(Uri contactUri) throws RequestFailedException {
101     try (Cursor cursor =
102         appContext
103             .getContentResolver()
104             .query(contactUri, new String[] {Contacts._ID}, null, null, null)) {
105       if (cursor == null || !cursor.moveToFirst()) {
106         throw new RequestFailedException("cannot get contact ID");
107       }
108       return cursor.getLong(0);
109     }
110   }
111 
112   private List<Long> getGoogleRawContactIds(long contactId) throws RequestFailedException {
113     List<Long> result = new ArrayList<>();
114     Selection selection =
115         Selection.column(RawContacts.CONTACT_ID)
116             .is("=", contactId)
117             .buildUpon()
118             .and(Selection.column(RawContacts.ACCOUNT_TYPE).is("=", "com.google"))
119             .build();
120     try (Cursor cursor =
121         appContext
122             .getContentResolver()
123             .query(
124                 RawContacts.CONTENT_URI,
125                 new String[] {RawContacts._ID, RawContacts.ACCOUNT_TYPE},
126                 selection.getSelection(),
127                 selection.getSelectionArgs(),
128                 null)) {
129       if (cursor == null) {
130         throw new RequestFailedException("null cursor from raw contact IDs");
131       }
132       for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
133         result.add(cursor.getLong(0));
134       }
135     }
136     return result;
137   }
138 }
139