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