OSDN Git Service

b/2531257 Will only display owner account if different than cal name
[android-x86/packages-apps-Calendar.git] / src / com / android / calendar / SelectCalendarsAdapter.java
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.calendar;
18
19 import android.accounts.AccountManager;
20 import android.accounts.AuthenticatorDescription;
21 import android.content.AsyncQueryHandler;
22 import android.content.ContentResolver;
23 import android.content.ContentUris;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.pm.PackageManager;
27 import android.database.Cursor;
28 import android.database.MatrixCursor;
29 import android.net.Uri;
30 import android.provider.Calendar.Calendars;
31 import android.text.TextUtils;
32 import android.util.Log;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.CursorTreeAdapter;
37 import android.widget.TextView;
38
39 import java.util.HashMap;
40 import java.util.Iterator;
41 import java.util.Map;
42
43 public class SelectCalendarsAdapter extends CursorTreeAdapter implements View.OnClickListener {
44
45     private static final String TAG = "Calendar";
46
47     // The drawables used for the button to change the visible and sync states on a calendar
48     private static final int[] SYNC_VIS_BUTTON_RES = new int[] {
49         R.drawable.widget_show,
50         R.drawable.widget_sync,
51         R.drawable.widget_off
52     };
53
54     private final LayoutInflater mInflater;
55     private final ContentResolver mResolver;
56     private final SelectCalendarsActivity mActivity;
57     private Map<String, AuthenticatorDescription> mTypeToAuthDescription
58         = new HashMap<String, AuthenticatorDescription>();
59     protected AuthenticatorDescription[] mAuthDescs;
60
61     // These track changes to the visible (selected) and synced state of calendars
62     private Map<Long, Boolean[]> mCalendarChanges
63         = new HashMap<Long, Boolean[]>();
64     private Map<Long, Boolean[]> mCalendarInitialStates
65         = new HashMap<Long, Boolean[]>();
66     private static final int SELECTED_INDEX = 0;
67     private static final int SYNCED_INDEX = 1;
68     private static final int CHANGES_SIZE = 2;
69
70     // This is for keeping MatrixCursor copies so that we can requery in the background.
71     private static Map<String, Cursor> mChildrenCursors
72         = new HashMap<String, Cursor>();
73
74     private static AsyncCalendarsUpdater mCalendarsUpdater;
75     // This is to keep our update tokens separate from other tokens. Since we cancel old updates
76     // when a new update comes in, we'd like to leave a token space that won't be canceled.
77     private static final int MIN_UPDATE_TOKEN = 1000;
78     private static int mUpdateToken = MIN_UPDATE_TOKEN;
79
80     private static String syncedVisible;
81     private static String syncedNotVisible;
82     private static String notSyncedNotVisible;
83
84     // This is to keep track of whether or not multiple calendars have the same display name
85     private static HashMap<String, Boolean> mIsDuplicateName = new HashMap<String, Boolean>();
86
87     private static final String[] PROJECTION = new String[] {
88       Calendars._ID,
89       Calendars._SYNC_ACCOUNT,
90       Calendars.OWNER_ACCOUNT,
91       Calendars.DISPLAY_NAME,
92       Calendars.COLOR,
93       Calendars.SELECTED,
94       Calendars.SYNC_EVENTS
95     };
96     //Keep these in sync with the projection
97     private static final int ID_COLUMN = 0;
98     private static final int ACCOUNT_COLUMN = 1;
99     private static final int OWNER_COLUMN = 2;
100     private static final int NAME_COLUMN = 3;
101     private static final int COLOR_COLUMN = 4;
102     private static final int SELECTED_COLUMN = 5;
103     private static final int SYNCED_COLUMN = 6;
104
105     private class AsyncCalendarsUpdater extends AsyncQueryHandler {
106
107         public AsyncCalendarsUpdater(ContentResolver cr) {
108             super(cr);
109         }
110
111         @Override
112         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
113             if(cursor == null) {
114                 return;
115             }
116
117             Cursor currentCursor = mChildrenCursors.get(cookie);
118             // Check if the new cursor has the same content as our old cursor
119             if (currentCursor != null) {
120                 if (compareCursors(currentCursor, cursor)) {
121                     cursor.close();
122                     return;
123                 } else {
124                     mActivity.stopManagingCursor(currentCursor);
125                     currentCursor.close();
126                     mChildrenCursors.remove(cookie);
127                 }
128             }
129             // If not then make a new matrix cursor for our Map
130             MatrixCursor newCursor = matrixCursorFromCursor(cursor);
131             // And update our list of duplicated names
132             Utils.checkForDuplicateNames(mIsDuplicateName, cursor, NAME_COLUMN);
133
134             mChildrenCursors.put((String)cookie, newCursor);
135             try {
136                 setChildrenCursor(token, newCursor);
137                 mActivity.startManagingCursor(newCursor);
138             } catch (NullPointerException e) {
139                 Log.w(TAG, "Adapter expired, try again on the next query: " + e.getMessage());
140             }
141             cursor.close();
142         }
143
144         /**
145          * Compares two cursors to see if they contain the same data.
146          *
147          * @return Returns true of the cursors contain the same data and are not null, false
148          * otherwise
149          */
150         private boolean compareCursors(Cursor c1, Cursor c2) {
151             if(c1 == null || c2 == null) {
152                 return false;
153             }
154
155             int numColumns = c1.getColumnCount();
156             if (numColumns != c2.getColumnCount()) {
157                 return false;
158             }
159
160             if (c1.getCount() != c2.getCount()) {
161                 return false;
162             }
163
164             c1.moveToPosition(-1);
165             c2.moveToPosition(-1);
166             while(c1.moveToNext() && c2.moveToNext()) {
167                 for(int i = 0; i < numColumns; i++) {
168                     if(!c1.getString(i).equals(c2.getString(i))) {
169                         return false;
170                     }
171                 }
172             }
173
174             return true;
175         }
176
177         private MatrixCursor matrixCursorFromCursor(Cursor cursor) {
178             MatrixCursor newCursor = new MatrixCursor(cursor.getColumnNames());
179             int numColumns = cursor.getColumnCount();
180             int count = cursor.getCount();
181             String data[] = new String[numColumns];
182             cursor.moveToPosition(-1);
183             while(cursor.moveToNext()) {
184                 for(int i = 0; i < numColumns; i++) {
185                     data[i] = cursor.getString(i);
186                 }
187                 newCursor.addRow(data);
188             }
189             return newCursor;
190         }
191     }
192
193     /**
194      * Method for changing the sync/vis state when a calendar's button is pressed.
195      *
196      * This gets called when the MultiStateButton for a calendar is clicked. It cycles the sync/vis
197      * state for the associated calendar and saves a change of state to a hashmap. It also compares
198      * against the original value and removes any changes from the hashmap if this is back
199      * at its initial state.
200      */
201     public void onClick(View v) {
202         View view = (View)v.getTag();
203         long id = (Long)view.getTag();
204         Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
205         String status = syncedNotVisible;
206         Boolean[] change;
207         Boolean[] initialState = mCalendarInitialStates.get(id);
208         if (mCalendarChanges.containsKey(id)) {
209             change = mCalendarChanges.get(id);
210         } else {
211             change = new Boolean[CHANGES_SIZE];
212             change[SELECTED_INDEX] = initialState[SELECTED_INDEX];
213             change[SYNCED_INDEX] = initialState[SYNCED_INDEX];
214             mCalendarChanges.put(id, change);
215         }
216
217         if (change[SELECTED_INDEX]) {
218             change[SELECTED_INDEX] = false;
219             status = syncedNotVisible;
220         }
221         else if (change[SYNCED_INDEX]) {
222             change[SYNCED_INDEX] = false;
223             status = notSyncedNotVisible;
224         }
225         else
226         {
227             change[SYNCED_INDEX] = true;
228             change[SELECTED_INDEX] = true;
229             status = syncedVisible;
230         }
231         setText(view, R.id.status, status);
232         if (change[SELECTED_INDEX] == initialState[SELECTED_INDEX] &&
233                 change[SYNCED_INDEX] == initialState[SYNCED_INDEX]) {
234             mCalendarChanges.remove(id);
235         }
236     }
237
238     public SelectCalendarsAdapter(Context context, Cursor cursor, SelectCalendarsActivity act) {
239         super(cursor, context);
240         syncedVisible = context.getString(R.string.synced_visible);
241         syncedNotVisible = context.getString(R.string.synced_not_visible);
242         notSyncedNotVisible = context.getString(R.string.not_synced_not_visible);
243
244         mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
245         mResolver = context.getContentResolver();
246         mActivity = act;
247         if (mCalendarsUpdater == null) {
248             mCalendarsUpdater = new AsyncCalendarsUpdater(mResolver);
249         }
250         if(cursor.getCount() == 0) {
251             //Should never happen since Calendar requires an account exist to use it.
252             Log.e(TAG, "SelectCalendarsAdapter: No accounts were returned!");
253         }
254         //Collect proper description for account types
255         mAuthDescs = AccountManager.get(context).getAuthenticatorTypes();
256         for (int i = 0; i < mAuthDescs.length; i++) {
257             mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]);
258         }
259     }
260
261     /*
262      * Write back the changes that have been made. The sync code will pick up any changes and
263      * do updates on its own.
264      */
265     public void doSaveAction() {
266         // Cancel the previous operation
267         mCalendarsUpdater.cancelOperation(mUpdateToken);
268         mUpdateToken++;
269         // This is to allow us to do queries and updates with the same AsyncQueryHandler without
270         // accidently canceling queries.
271         if(mUpdateToken < MIN_UPDATE_TOKEN) mUpdateToken = MIN_UPDATE_TOKEN;
272
273         Iterator<Long> changeKeys = mCalendarChanges.keySet().iterator();
274         while (changeKeys.hasNext()) {
275             long id = changeKeys.next();
276             Boolean[] change = mCalendarChanges.get(id);
277             int newSelected = change[SELECTED_INDEX] ? 1 : 0;
278             int newSynced = change[SYNCED_INDEX] ? 1 : 0;
279
280             Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
281             ContentValues values = new ContentValues();
282             values.put(Calendars.SELECTED, newSelected);
283             values.put(Calendars.SYNC_EVENTS, newSynced);
284             mCalendarsUpdater.startUpdate(mUpdateToken, id, uri, values, null, null);
285         }
286     }
287
288     private static void setText(View view, int id, String text) {
289         if (TextUtils.isEmpty(text)) {
290             return;
291         }
292         TextView textView = (TextView) view.findViewById(id);
293         textView.setText(text);
294     }
295
296     /**
297      * Gets the label associated with a particular account type. If none found, return null.
298      * @param accountType the type of account
299      * @return a CharSequence for the label or null if one cannot be found.
300      */
301     protected CharSequence getLabelForType(final String accountType) {
302         CharSequence label = null;
303         if (mTypeToAuthDescription.containsKey(accountType)) {
304              try {
305                  AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
306                  Context authContext = mActivity.createPackageContext(desc.packageName, 0);
307                  label = authContext.getResources().getText(desc.labelId);
308              } catch (PackageManager.NameNotFoundException e) {
309                  Log.w(TAG, "No label for account type " + ", type " + accountType);
310              }
311         }
312         return label;
313     }
314
315     @Override
316     protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
317         String account = cursor.getString(ACCOUNT_COLUMN);
318         String status = notSyncedNotVisible;
319         int state = 2;
320         int position = cursor.getPosition();
321         long id = cursor.getLong(ID_COLUMN);
322
323         // First see if the user has already changed the state of this calendar
324         Boolean[] initialState = mCalendarChanges.get(id);
325         // if not just grab the initial state
326         if (initialState == null) {
327             initialState = mCalendarInitialStates.get(id);
328         }
329         // and create a new initial state if we've never seen this calendar before.
330         if(initialState == null) {
331             initialState = new Boolean[CHANGES_SIZE];
332             initialState[SELECTED_INDEX] = cursor.getInt(SELECTED_COLUMN) == 1;
333             initialState[SYNCED_INDEX] = cursor.getInt(SYNCED_COLUMN) == 1;
334             mCalendarInitialStates.put(id, initialState);
335         }
336
337         if(initialState[SYNCED_INDEX]) {
338             if(initialState[SELECTED_INDEX]) {
339                 status = syncedVisible;
340                 state = 0;
341             } else {
342                 status = syncedNotVisible;
343                 state = 1;
344             }
345         }
346
347         view.findViewById(R.id.color)
348             .setBackgroundDrawable(Utils.getColorChip(cursor.getInt(COLOR_COLUMN)));
349         String name = cursor.getString(NAME_COLUMN);
350         String owner = cursor.getString(OWNER_COLUMN);
351         if (mIsDuplicateName.containsKey(name) && mIsDuplicateName.get(name) &&
352                 !name.equalsIgnoreCase(owner)) {
353             name = new StringBuilder(name)
354                     .append(Utils.OPEN_EMAIL_MARKER)
355                     .append(owner)
356                     .append(Utils.CLOSE_EMAIL_MARKER)
357                     .toString();
358         }
359         setText(view, R.id.calendar, name);
360         setText(view, R.id.status, status);
361         MultiStateButton button = (MultiStateButton) view.findViewById(R.id.multiStateButton);
362
363         //Set up the listeners so a click on the button will change the state.
364         //The view already uses the onChildClick method in the activity.
365         button.setTag(view);
366         view.setTag(id);
367         button.setOnClickListener(this);
368         button.setButtonResources(SYNC_VIS_BUTTON_RES);
369         button.setState(state);
370     }
371
372     @Override
373     protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
374         int accountColumn = cursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT);
375         int accountTypeColumn = cursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT_TYPE);
376         String account = cursor.getString(accountColumn);
377         String accountType = cursor.getString(accountTypeColumn);
378         setText(view, R.id.account, account);
379         setText(view, R.id.account_type, getLabelForType(accountType).toString());
380     }
381
382     @Override
383     protected Cursor getChildrenCursor(Cursor groupCursor) {
384         int accountColumn = groupCursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT);
385         String account = groupCursor.getString(accountColumn);
386         //Get all the calendars for just this account.
387         Cursor childCursor = mChildrenCursors.get(account);
388         mCalendarsUpdater.startQuery(groupCursor.getPosition(),
389                 account,
390                 Calendars.CONTENT_URI, PROJECTION,
391                 Calendars._SYNC_ACCOUNT + "=\"" + account + "\"" /*Selection*/,
392                 null /* selectionArgs */,
393                 Calendars.DISPLAY_NAME);
394         return childCursor;
395     }
396
397     @Override
398     protected View newChildView(Context context, Cursor cursor, boolean isLastChild,
399             ViewGroup parent) {
400         return mInflater.inflate(R.layout.calendar_item, parent, false);
401     }
402
403     @Override
404     protected View newGroupView(Context context, Cursor cursor, boolean isExpanded,
405             ViewGroup parent) {
406         return mInflater.inflate(R.layout.account_item, parent, false);
407     }
408 }