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 
17 package com.android.phone;
18 
19 import static android.view.Window.PROGRESS_VISIBILITY_OFF;
20 import static android.view.Window.PROGRESS_VISIBILITY_ON;
21 
22 import android.app.ListActivity;
23 import android.content.AsyncQueryHandler;
24 import android.content.ContentResolver;
25 import android.content.Intent;
26 import android.database.Cursor;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.text.BidiFormatter;
30 import android.text.TextDirectionHeuristics;
31 import android.util.Log;
32 import android.view.View;
33 import android.view.Window;
34 import android.widget.CursorAdapter;
35 import android.widget.SimpleCursorAdapter;
36 import android.widget.TextView;
37 
38 /**
39  * Abbreviated Dial Numbers (ADN) list activity for the Phone app. By default, this class will show
40  * you all Service Dialing Numbers (SDN) that are supported by a service provider.  SDNs are a form
41  * of speed dial for accessing service provider contacts like "#MIN" for getting user minutes.
42  * To see this class in use, trigger the radio info screen by dialing *#*#INFO#*#* and open the
43  * menu.
44  * This class can also be used as a base class for simple contact lists that can be represented with
45  * only labels and numbers.
46  */
47 public class ADNList extends ListActivity {
48     protected static final String TAG = "ADNList";
49     protected static final boolean DBG = false;
50 
51     private static final String[] COLUMN_NAMES = new String[] {
52         "name",
53         "number",
54         "emails"
55     };
56 
57     protected static final int NAME_COLUMN = 0;
58     protected static final int NUMBER_COLUMN = 1;
59     protected static final int EMAILS_COLUMN = 2;
60 
61     private static final int[] VIEW_NAMES = new int[] {
62         android.R.id.text1,
63         android.R.id.text2
64     };
65 
66     protected static final int QUERY_TOKEN = 0;
67     protected static final int INSERT_TOKEN = 1;
68     protected static final int UPDATE_TOKEN = 2;
69     protected static final int DELETE_TOKEN = 3;
70 
71 
72     protected QueryHandler mQueryHandler;
73     protected CursorAdapter mCursorAdapter;
74     protected Cursor mCursor = null;
75 
76     private TextView mEmptyText;
77 
78     protected int mInitialSelection = -1;
79 
80     @Override
onCreate(Bundle icicle)81     protected void onCreate(Bundle icicle) {
82         super.onCreate(icicle);
83         getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
84         setContentView(R.layout.adn_list);
85         mEmptyText = (TextView) findViewById(android.R.id.empty);
86         mQueryHandler = new QueryHandler(getContentResolver());
87     }
88 
89     @Override
onResume()90     protected void onResume() {
91         super.onResume();
92         query();
93     }
94 
95     @Override
onStop()96     protected void onStop() {
97         super.onStop();
98         if (mCursor != null) {
99             mCursor.deactivate();
100         }
101     }
102 
resolveIntent()103     protected Uri resolveIntent() {
104         Intent intent = getIntent();
105         if (intent.getData() == null) {
106             intent.setData(Uri.parse("content://icc/adn"));
107         }
108 
109         return intent.getData();
110     }
111 
query()112     private void query() {
113         Uri uri = resolveIntent();
114         if (DBG) log("query: starting an async query");
115         mQueryHandler.startQuery(QUERY_TOKEN, null, uri, COLUMN_NAMES,
116                 null, null, null);
117         displayProgress(true);
118     }
119 
reQuery()120     private void reQuery() {
121         query();
122     }
123 
setAdapter()124     private void setAdapter() {
125         // NOTE:
126         // As it it written, the positioning code below is NOT working.
127         // However, this current non-working state is in compliance with
128         // the UI paradigm, so we can't really do much to change it.
129 
130         // In the future, if we wish to get this "positioning" correct,
131         // we'll need to do the following:
132         //   1. Change the layout to in the cursor adapter to:
133         //     android.R.layout.simple_list_item_checked
134         //   2. replace the selection / focus code with:
135         //     getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
136         //     getListView().setItemChecked(mInitialSelection, true);
137 
138         // Since the positioning is really only useful for the dialer's
139         // SpecialCharSequence case (dialing '2#' to get to the 2nd
140         // contact for instance), it doesn't make sense to mess with
141         // the usability of the activity just for this case.
142 
143         // These artifacts include:
144         //  1. UI artifacts (checkbox and highlight at the same time)
145         //  2. Allowing the user to edit / create new SIM contacts when
146         //    the user is simply trying to retrieve a number into the d
147         //    dialer.
148 
149         if (mCursorAdapter == null) {
150             mCursorAdapter = newAdapter();
151 
152             setListAdapter(mCursorAdapter);
153         } else {
154             mCursorAdapter.changeCursor(mCursor);
155         }
156 
157         if (mInitialSelection >=0 && mInitialSelection < mCursorAdapter.getCount()) {
158             setSelection(mInitialSelection);
159             getListView().setFocusableInTouchMode(true);
160             boolean gotfocus = getListView().requestFocus();
161         }
162     }
163 
newAdapter()164     protected CursorAdapter newAdapter() {
165         SimpleCursorAdapter sca = new SimpleCursorAdapter(this,
166                 android.R.layout.simple_list_item_2,
167                 mCursor, COLUMN_NAMES, VIEW_NAMES);
168 
169         // This code block is for displaying a phone number including "+ country code" correctly
170         // in bidirectional language (b/35180168).
171         // Without this code, e.g. "+0123456789" is wrongly displayed as "0123456789+".
172         sca.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
173             public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
174                 view.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
175                 if (columnIndex == NUMBER_COLUMN) {
176                     String num = cursor.getString(NUMBER_COLUMN);
177                     if (num != null) {
178                         BidiFormatter bf = BidiFormatter.getInstance();
179                         num = bf.unicodeWrap(num, TextDirectionHeuristics.LTR, true);
180                     }
181                     if (view instanceof TextView) {
182                         ((TextView) view).setText(num);
183                     }
184                     return true;
185                 }
186                 return false;
187             }
188         });
189         return sca;
190     }
191 
displayProgress(boolean loading)192     private void displayProgress(boolean loading) {
193         if (DBG) log("displayProgress: " + loading);
194 
195         mEmptyText.setText(loading ? R.string.simContacts_emptyLoading:
196                 R.string.simContacts_empty);
197         getWindow().setFeatureInt(
198                 Window.FEATURE_INDETERMINATE_PROGRESS,
199                 loading ? PROGRESS_VISIBILITY_ON : PROGRESS_VISIBILITY_OFF);
200     }
201 
202     private class QueryHandler extends AsyncQueryHandler {
QueryHandler(ContentResolver cr)203         public QueryHandler(ContentResolver cr) {
204             super(cr);
205         }
206 
207         @Override
onQueryComplete(int token, Object cookie, Cursor c)208         protected void onQueryComplete(int token, Object cookie, Cursor c) {
209             if (DBG) log("onQueryComplete: cursor.count=" + c.getCount());
210             mCursor = c;
211             setAdapter();
212             displayProgress(false);
213 
214             // Cursor is refreshed and inherited classes may have menu items depending on it.
215             invalidateOptionsMenu();
216         }
217 
218         @Override
onInsertComplete(int token, Object cookie, Uri uri)219         protected void onInsertComplete(int token, Object cookie, Uri uri) {
220             if (DBG) log("onInsertComplete: requery");
221             reQuery();
222         }
223 
224         @Override
onUpdateComplete(int token, Object cookie, int result)225         protected void onUpdateComplete(int token, Object cookie, int result) {
226             if (DBG) log("onUpdateComplete: requery");
227             reQuery();
228         }
229 
230         @Override
onDeleteComplete(int token, Object cookie, int result)231         protected void onDeleteComplete(int token, Object cookie, int result) {
232             if (DBG) log("onDeleteComplete: requery");
233             reQuery();
234         }
235     }
236 
log(String msg)237     protected void log(String msg) {
238         Log.d(TAG, "[ADNList] " + msg);
239     }
240 }
241