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