AttachPhotoActivity.java revision ebc17929007ecf12f25b8e90023766e8a083d11e
1/*
2 * Copyright (C) 2006 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
17package com.android.contacts.activities;
18
19import com.android.contacts.ContactLoader;
20import com.android.contacts.ContactLoader.Result;
21import com.android.contacts.ContactSaveService;
22import com.android.contacts.ContactsActivity;
23import com.android.contacts.ContactsUtils;
24import com.android.contacts.model.AccountType;
25import com.android.contacts.model.EntityDelta;
26import com.android.contacts.model.EntityDeltaList;
27import com.android.contacts.model.EntityModifier;
28import com.android.contacts.util.ContactPhotoUtils;
29
30import android.content.ContentResolver;
31import android.content.Intent;
32import android.content.Loader;
33import android.content.Loader.OnLoadCompleteListener;
34import android.database.Cursor;
35import android.graphics.Bitmap;
36import android.graphics.BitmapFactory;
37import android.net.Uri;
38import android.os.Bundle;
39import android.provider.ContactsContract.CommonDataKinds.Photo;
40import android.provider.ContactsContract.Contacts;
41import android.provider.ContactsContract.DisplayPhoto;
42import android.provider.MediaStore;
43import android.util.Log;
44
45import java.io.File;
46
47/**
48 * Provides an external interface for other applications to attach images
49 * to contacts. It will first present a contact picker and then run the
50 * image that is handed to it through the cropper to make the image the proper
51 * size and give the user a chance to use the face detector.
52 */
53public class AttachPhotoActivity extends ContactsActivity {
54    private static final String TAG = AttachPhotoActivity.class.getSimpleName();
55
56    private static final int REQUEST_PICK_CONTACT = 1;
57    private static final int REQUEST_CROP_PHOTO = 2;
58
59    private static final String KEY_CONTACT_URI = "contact_uri";
60    private static final String KEY_TEMP_PHOTO_URI = "temp_photo_uri";
61
62    private File mTempPhotoFile;
63    private Uri mTempPhotoUri;
64
65    private ContentResolver mContentResolver;
66
67    // Height and width (in pixels) to request for the photo - queried from the provider.
68    private static int mPhotoDim;
69
70    private Uri mContactUri;
71
72    @Override
73    public void onCreate(Bundle icicle) {
74        super.onCreate(icicle);
75
76        if (icicle != null) {
77            final String uri = icicle.getString(KEY_CONTACT_URI);
78            mContactUri = (uri == null) ? null : Uri.parse(uri);
79
80            mTempPhotoUri = Uri.parse(icicle.getString(KEY_TEMP_PHOTO_URI));
81            mTempPhotoFile = new File(mTempPhotoUri.getPath());
82        } else {
83            mTempPhotoFile = ContactPhotoUtils.generateTempPhotoFile(this);
84            mTempPhotoUri = Uri.fromFile(mTempPhotoFile);
85
86            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
87            intent.setType(Contacts.CONTENT_ITEM_TYPE);
88            startActivityForResult(intent, REQUEST_PICK_CONTACT);
89        }
90
91        mContentResolver = getContentResolver();
92
93        // Load the photo dimension to request.
94        Cursor c = mContentResolver.query(DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
95                new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null);
96        try {
97            c.moveToFirst();
98            mPhotoDim = c.getInt(0);
99        } finally {
100            c.close();
101        }
102    }
103
104    @Override
105    protected void onSaveInstanceState(Bundle outState) {
106        super.onSaveInstanceState(outState);
107        if (mContactUri != null) outState.putString(KEY_CONTACT_URI, mContactUri.toString());
108        outState.putString(KEY_TEMP_PHOTO_URI, mTempPhotoUri.toString());
109    }
110
111    @Override
112    protected void onActivityResult(int requestCode, int resultCode, Intent result) {
113        if (resultCode != RESULT_OK) {
114            finish();
115            return;
116        }
117
118        if (requestCode == REQUEST_PICK_CONTACT) {
119            // A contact was picked. Launch the cropper to get face detection, the right size, etc.
120            // TODO: get these values from constants somewhere
121            Intent myIntent = getIntent();
122            Intent intent = new Intent("com.android.camera.action.CROP", myIntent.getData());
123            if (myIntent.getStringExtra("mimeType") != null) {
124                intent.setDataAndType(myIntent.getData(), myIntent.getStringExtra("mimeType"));
125            }
126            intent.putExtra("crop", "true");
127            intent.putExtra("aspectX", 1);
128            intent.putExtra("aspectY", 1);
129            intent.putExtra("outputX", mPhotoDim);
130            intent.putExtra("outputY", mPhotoDim);
131            intent.putExtra(MediaStore.EXTRA_OUTPUT, mTempPhotoUri);
132
133            startActivityForResult(intent, REQUEST_CROP_PHOTO);
134
135            mContactUri = result.getData();
136
137        } else if (requestCode == REQUEST_CROP_PHOTO) {
138            loadContact(mContactUri, new Listener() {
139                @Override
140                public void onContactLoaded(ContactLoader.Result contact) {
141                    saveContact(contact);
142                }
143            });
144        }
145    }
146
147    // TODO: consider moving this to ContactLoader, especially if we keep adding similar
148    // code elsewhere (ViewNotificationService is another case).  The only concern is that,
149    // although this is convenient, it isn't quite as robust as using LoaderManager... for
150    // instance, the loader doesn't persist across Activity restarts.
151    private void loadContact(Uri contactUri, final Listener listener) {
152        final ContactLoader loader = new ContactLoader(this, contactUri);
153        loader.registerListener(0, new OnLoadCompleteListener<ContactLoader.Result>() {
154            @Override
155            public void onLoadComplete(
156                    Loader<ContactLoader.Result> loader, ContactLoader.Result contact) {
157                try {
158                    loader.reset();
159                }
160                catch (RuntimeException e) {
161                    Log.e(TAG, "Error resetting loader", e);
162                }
163                listener.onContactLoaded(contact);
164            }
165        });
166        loader.startLoading();
167    }
168
169    private interface Listener {
170        public void onContactLoaded(Result contact);
171    }
172
173    /**
174     * If prerequisites have been met, attach the photo to a raw-contact and save.
175     * The prerequisites are:
176     * - photo has been cropped
177     * - contact has been loaded
178     */
179    private void saveContact(ContactLoader.Result contact) {
180
181        // Obtain the raw-contact that we will save to.
182        EntityDeltaList deltaList = contact.createEntityDeltaList();
183        EntityDelta raw = deltaList.getFirstWritableRawContact(this);
184        if (raw == null) {
185            Log.w(TAG, "no writable raw-contact found");
186            return;
187        }
188
189        // Create a scaled, compressed bitmap to add to the entity-delta list.
190        final int size = ContactsUtils.getThumbnailSize(this);
191        final Bitmap bitmap = BitmapFactory.decodeFile(mTempPhotoFile.getAbsolutePath());
192        final Bitmap scaled = Bitmap.createScaledBitmap(bitmap, size, size, false);
193        final byte[] compressed = ContactPhotoUtils.compressBitmap(scaled);
194        if (compressed == null) {
195            Log.w(TAG, "could not create scaled and compressed Bitmap");
196            return;
197        }
198
199        // Add compressed bitmap to entity-delta... this allows us to save to
200        // a new contact; otherwise the entity-delta-list would be empty, and
201        // the ContactSaveService would not create the new contact, and the
202        // full-res photo would fail to be saved to the non-existent contact.
203        AccountType account = raw.getRawContactAccountType(this);
204        EntityDelta.ValuesDelta values =
205                EntityModifier.ensureKindExists(raw, account, Photo.CONTENT_ITEM_TYPE);
206        if (values == null) {
207            Log.w(TAG, "cannot attach photo to this account type");
208            return;
209        }
210        values.put(Photo.PHOTO, compressed);
211
212        // Finally, invoke the ContactSaveService.
213        Log.v(TAG, "all prerequisites met, about to save photo to contact");
214        Intent intent = ContactSaveService.createSaveContactIntent(
215                this,
216                deltaList,
217                "", 0,
218                contact.isUserProfile(),
219                null, null,
220                raw.getRawContactId(),
221                mTempPhotoFile.getAbsolutePath());
222        startService(intent);
223        finish();
224    }
225}
226