1/*
2 * Copyright (C) 2007 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.phone;
18
19import android.accounts.Account;
20import android.app.ActionBar;
21import android.app.ProgressDialog;
22import android.content.ContentProviderOperation;
23import android.content.ContentResolver;
24import android.content.ContentValues;
25import android.content.DialogInterface;
26import android.content.DialogInterface.OnCancelListener;
27import android.content.DialogInterface.OnClickListener;
28import android.content.Intent;
29import android.content.OperationApplicationException;
30import android.database.Cursor;
31import android.net.Uri;
32import android.os.Bundle;
33import android.os.RemoteException;
34import android.provider.ContactsContract;
35import android.provider.ContactsContract.CommonDataKinds.Email;
36import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
37import android.provider.ContactsContract.CommonDataKinds.Phone;
38import android.provider.ContactsContract.CommonDataKinds.StructuredName;
39import android.provider.ContactsContract.Data;
40import android.provider.ContactsContract.RawContacts;
41import android.telecom.PhoneAccount;
42import android.text.TextUtils;
43import android.util.Log;
44import android.view.ContextMenu;
45import android.view.KeyEvent;
46import android.view.Menu;
47import android.view.MenuItem;
48import android.view.View;
49import android.widget.AdapterView;
50import android.widget.CursorAdapter;
51import android.widget.ListView;
52import android.widget.SimpleCursorAdapter;
53import android.widget.TextView;
54
55import java.util.ArrayList;
56
57/**
58 * SIM Address Book UI for the Phone app.
59 */
60public class SimContacts extends ADNList {
61    private static final String LOG_TAG = "SimContacts";
62
63    static final ContentValues sEmptyContentValues = new ContentValues();
64
65    private static final int MENU_IMPORT_ONE = 1;
66    private static final int MENU_IMPORT_ALL = 2;
67    private ProgressDialog mProgressDialog;
68
69    private Account mAccount;
70
71    private static class NamePhoneTypePair {
72        final String name;
73        final int phoneType;
74        public NamePhoneTypePair(String nameWithPhoneType) {
75            // Look for /W /H /M or /O at the end of the name signifying the type
76            int nameLen = nameWithPhoneType.length();
77            if (nameLen - 2 >= 0 && nameWithPhoneType.charAt(nameLen - 2) == '/') {
78                char c = Character.toUpperCase(nameWithPhoneType.charAt(nameLen - 1));
79                if (c == 'W') {
80                    phoneType = Phone.TYPE_WORK;
81                } else if (c == 'M' || c == 'O') {
82                    phoneType = Phone.TYPE_MOBILE;
83                } else if (c == 'H') {
84                    phoneType = Phone.TYPE_HOME;
85                } else {
86                    phoneType = Phone.TYPE_OTHER;
87                }
88                name = nameWithPhoneType.substring(0, nameLen - 2);
89            } else {
90                phoneType = Phone.TYPE_OTHER;
91                name = nameWithPhoneType;
92            }
93        }
94    }
95
96    private class ImportAllSimContactsThread extends Thread
97            implements OnCancelListener, OnClickListener {
98
99        boolean mCanceled = false;
100
101        public ImportAllSimContactsThread() {
102            super("ImportAllSimContactsThread");
103        }
104
105        @Override
106        public void run() {
107            final ContentValues emptyContentValues = new ContentValues();
108            final ContentResolver resolver = getContentResolver();
109
110            mCursor.moveToPosition(-1);
111            while (!mCanceled && mCursor.moveToNext()) {
112                actuallyImportOneSimContact(mCursor, resolver, mAccount);
113                mProgressDialog.incrementProgressBy(1);
114            }
115
116            mProgressDialog.dismiss();
117            finish();
118        }
119
120        public void onCancel(DialogInterface dialog) {
121            mCanceled = true;
122        }
123
124        public void onClick(DialogInterface dialog, int which) {
125            if (which == DialogInterface.BUTTON_NEGATIVE) {
126                mCanceled = true;
127                mProgressDialog.dismiss();
128            } else {
129                Log.e(LOG_TAG, "Unknown button event has come: " + dialog.toString());
130            }
131        }
132    }
133
134    private static void actuallyImportOneSimContact(
135            final Cursor cursor, final ContentResolver resolver, Account account) {
136        final NamePhoneTypePair namePhoneTypePair =
137            new NamePhoneTypePair(cursor.getString(NAME_COLUMN));
138        final String name = namePhoneTypePair.name;
139        final int phoneType = namePhoneTypePair.phoneType;
140        final String phoneNumber = cursor.getString(NUMBER_COLUMN);
141        final String emailAddresses = cursor.getString(EMAILS_COLUMN);
142        final String[] emailAddressArray;
143        if (!TextUtils.isEmpty(emailAddresses)) {
144            emailAddressArray = emailAddresses.split(",");
145        } else {
146            emailAddressArray = null;
147        }
148
149        final ArrayList<ContentProviderOperation> operationList =
150            new ArrayList<ContentProviderOperation>();
151        ContentProviderOperation.Builder builder =
152            ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
153        String myGroupsId = null;
154        if (account != null) {
155            builder.withValue(RawContacts.ACCOUNT_NAME, account.name);
156            builder.withValue(RawContacts.ACCOUNT_TYPE, account.type);
157        } else {
158            builder.withValues(sEmptyContentValues);
159        }
160        operationList.add(builder.build());
161
162        builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
163        builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
164        builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
165        builder.withValue(StructuredName.DISPLAY_NAME, name);
166        operationList.add(builder.build());
167
168        builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
169        builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
170        builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
171        builder.withValue(Phone.TYPE, phoneType);
172        builder.withValue(Phone.NUMBER, phoneNumber);
173        builder.withValue(Data.IS_PRIMARY, 1);
174        operationList.add(builder.build());
175
176        if (emailAddresses != null) {
177            for (String emailAddress : emailAddressArray) {
178                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
179                builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
180                builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
181                builder.withValue(Email.TYPE, Email.TYPE_MOBILE);
182                builder.withValue(Email.DATA, emailAddress);
183                operationList.add(builder.build());
184            }
185        }
186
187        if (myGroupsId != null) {
188            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
189            builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
190            builder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
191            builder.withValue(GroupMembership.GROUP_SOURCE_ID, myGroupsId);
192            operationList.add(builder.build());
193        }
194
195        try {
196            resolver.applyBatch(ContactsContract.AUTHORITY, operationList);
197        } catch (RemoteException e) {
198            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
199        } catch (OperationApplicationException e) {
200            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
201        }
202    }
203
204    private void importOneSimContact(int position) {
205        final ContentResolver resolver = getContentResolver();
206        if (mCursor.moveToPosition(position)) {
207            actuallyImportOneSimContact(mCursor, resolver, mAccount);
208        } else {
209            Log.e(LOG_TAG, "Failed to move the cursor to the position \"" + position + "\"");
210        }
211    }
212
213    /* Followings are overridden methods */
214
215    @Override
216    protected void onCreate(Bundle icicle) {
217        super.onCreate(icicle);
218
219        Intent intent = getIntent();
220        if (intent != null) {
221            final String accountName = intent.getStringExtra("account_name");
222            final String accountType = intent.getStringExtra("account_type");
223            if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
224                mAccount = new Account(accountName, accountType);
225            }
226        }
227
228        registerForContextMenu(getListView());
229
230        ActionBar actionBar = getActionBar();
231        if (actionBar != null) {
232            // android.R.id.home will be triggered in onOptionsItemSelected()
233            actionBar.setDisplayHomeAsUpEnabled(true);
234        }
235    }
236
237    @Override
238    protected CursorAdapter newAdapter() {
239        return new SimpleCursorAdapter(this, R.layout.sim_import_list_entry, mCursor,
240                new String[] { "name" }, new int[] { android.R.id.text1 });
241    }
242
243    @Override
244    protected Uri resolveIntent() {
245        Intent intent = getIntent();
246        intent.setData(Uri.parse("content://icc/adn"));
247        if (Intent.ACTION_PICK.equals(intent.getAction())) {
248            // "index" is 1-based
249            mInitialSelection = intent.getIntExtra("index", 0) - 1;
250        }
251        return intent.getData();
252    }
253
254    @Override
255    public boolean onCreateOptionsMenu(Menu menu) {
256        super.onCreateOptionsMenu(menu);
257        menu.add(0, MENU_IMPORT_ALL, 0, R.string.importAllSimEntries);
258        return true;
259    }
260
261    @Override
262    public boolean onPrepareOptionsMenu(Menu menu) {
263        MenuItem item = menu.findItem(MENU_IMPORT_ALL);
264        if (item != null) {
265            item.setVisible(mCursor != null && mCursor.getCount() > 0);
266        }
267        return super.onPrepareOptionsMenu(menu);
268    }
269
270    @Override
271    public boolean onOptionsItemSelected(MenuItem item) {
272        switch (item.getItemId()) {
273            case android.R.id.home:
274                onBackPressed();
275                return true;
276            case MENU_IMPORT_ALL:
277                CharSequence title = getString(R.string.importAllSimEntries);
278                CharSequence message = getString(R.string.importingSimContacts);
279
280                ImportAllSimContactsThread thread = new ImportAllSimContactsThread();
281
282                // TODO: need to show some error dialog.
283                if (mCursor == null) {
284                    Log.e(LOG_TAG, "cursor is null. Ignore silently.");
285                    break;
286                }
287                mProgressDialog = new ProgressDialog(this);
288                mProgressDialog.setTitle(title);
289                mProgressDialog.setMessage(message);
290                mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
291                mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
292                        getString(R.string.cancel), thread);
293                mProgressDialog.setProgress(0);
294                mProgressDialog.setMax(mCursor.getCount());
295                mProgressDialog.show();
296
297                thread.start();
298
299                return true;
300        }
301        return super.onOptionsItemSelected(item);
302    }
303
304    @Override
305    public boolean onContextItemSelected(MenuItem item) {
306        switch (item.getItemId()) {
307            case MENU_IMPORT_ONE:
308                ContextMenu.ContextMenuInfo menuInfo = item.getMenuInfo();
309                if (menuInfo instanceof AdapterView.AdapterContextMenuInfo) {
310                    int position = ((AdapterView.AdapterContextMenuInfo)menuInfo).position;
311                    importOneSimContact(position);
312                    return true;
313                }
314        }
315        return super.onContextItemSelected(item);
316    }
317
318    @Override
319    public void onCreateContextMenu(ContextMenu menu, View v,
320            ContextMenu.ContextMenuInfo menuInfo) {
321        if (menuInfo instanceof AdapterView.AdapterContextMenuInfo) {
322            AdapterView.AdapterContextMenuInfo itemInfo =
323                    (AdapterView.AdapterContextMenuInfo) menuInfo;
324            TextView textView = (TextView) itemInfo.targetView.findViewById(android.R.id.text1);
325            if (textView != null) {
326                menu.setHeaderTitle(textView.getText());
327            }
328            menu.add(0, MENU_IMPORT_ONE, 0, R.string.importSimEntry);
329        }
330    }
331
332    @Override
333    public void onListItemClick(ListView l, View v, int position, long id) {
334        importOneSimContact(position);
335    }
336
337    @Override
338    public boolean onKeyDown(int keyCode, KeyEvent event) {
339        switch (keyCode) {
340            case KeyEvent.KEYCODE_CALL: {
341                if (mCursor != null && mCursor.moveToPosition(getSelectedItemPosition())) {
342                    String phoneNumber = mCursor.getString(NUMBER_COLUMN);
343                    if (phoneNumber == null || !TextUtils.isGraphic(phoneNumber)) {
344                        // There is no number entered.
345                        //TODO play error sound or something...
346                        return true;
347                    }
348                    Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
349                            Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null));
350                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
351                                          | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
352                    startActivity(intent);
353                    finish();
354                    return true;
355                }
356            }
357        }
358        return super.onKeyDown(keyCode, event);
359    }
360}
361