OSDN Git Service

b/2432256 Rewrite of Calendars to use HashMaps and async updates.
[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 java.util.HashMap;
20 import java.util.Iterator;
21 import java.util.Map;
22
23 import android.accounts.AccountManager;
24 import android.accounts.AuthenticatorDescription;
25 import android.content.AsyncQueryHandler;
26 import android.content.ContentResolver;
27 import android.content.ContentUris;
28 import android.content.ContentValues;
29 import android.content.Context;
30 import android.content.pm.PackageManager;
31 import android.database.Cursor;
32 import android.graphics.drawable.Drawable;
33 import android.graphics.drawable.GradientDrawable;
34 import android.net.Uri;
35 import android.provider.Calendar.Calendars;
36 import android.text.TextUtils;
37 import android.util.Log;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.widget.CursorTreeAdapter;
42 import android.widget.TextView;
43
44 public class SelectCalendarsAdapter extends CursorTreeAdapter implements View.OnClickListener {
45
46     private static final int CLEAR_ALPHA_MASK = 0x00FFFFFF;
47     private static final int HIGH_ALPHA = 255 << 24;
48     private static final int MED_ALPHA = 180 << 24;
49     private static final int LOW_ALPHA = 150 << 24;
50
51     /* The corner should be rounded on the top right and bottom right */
52     private static final float[] CORNERS = new float[] {0, 0, 5, 5, 5, 5, 0, 0};
53
54     private static final String TAG = "Calendar";
55
56     //TODO replace with final icon names
57     private static final int[] SYNC_VIS_BUTTON_RES = new int[] {
58         R.drawable.widget_show,
59         R.drawable.widget_sync,
60         R.drawable.widget_off
61     };
62
63     private final LayoutInflater mInflater;
64     private final ContentResolver mResolver;
65     private final SelectCalendarsActivity mActivity;
66     private Map<String, AuthenticatorDescription> mTypeToAuthDescription
67         = new HashMap<String, AuthenticatorDescription>();
68     protected AuthenticatorDescription[] mAuthDescs;
69
70     private Map<Long, Boolean[]> mCalendarChanges
71         = new HashMap<Long, Boolean[]>();
72     private Map<Long, Boolean[]> mCalendarInitialStates
73         = new HashMap<Long, Boolean[]>();
74     private static final int SELECTED_INDEX = 0;
75     private static final int SYNCED_INDEX = 1;
76     private static final int CHANGES_SIZE = 2;
77
78     private static AsyncCalendarsUpdater mCalendarsUpdater;
79     private static int mUpdateToken = 0;
80
81     private static String syncedVisible;
82     private static String syncedNotVisible;
83     private static String notSyncedNotVisible;
84
85     private static final String[] PROJECTION = new String[] {
86       Calendars._ID,
87       Calendars._SYNC_ACCOUNT,
88       Calendars.DISPLAY_NAME,
89       Calendars.COLOR,
90       Calendars.SELECTED,
91       Calendars.SYNC_EVENTS
92     };
93
94     //Keep these in sync with the projection
95     private static final int ID_COLUMN = 0;
96     private static final int ACCOUNT_COLUMN = 1;
97     private static final int NAME_COLUMN = 2;
98     private static final int COLOR_COLUMN = 3;
99     private static final int SELECTED_COLUMN = 4;
100     private static final int SYNCED_COLUMN = 5;
101
102     private class AsyncCalendarsUpdater extends AsyncQueryHandler {
103         public AsyncCalendarsUpdater(ContentResolver cr) {
104             super(cr);
105         }
106     }
107
108     /**
109      * Method for changing the sync/vis state when a calendar's button is pressed.
110      *
111      * This gets called when the MultiStateButton for a calendar is clicked. It cycles the sync/vis
112      * state for the associated calendar and saves a change of state to a hashmap. It also compares
113      * against the original value and removes any changes from the hashmap if this is back
114      * at its initial state.
115      */
116     public void onClick(View v) {
117         View view = (View)v.getTag();
118         long id = (Long)view.getTag();
119         Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
120         String status = syncedNotVisible;
121         Boolean[] change;
122         Boolean[] initialState = mCalendarInitialStates.get(id);
123         if (mCalendarChanges.containsKey(id)) {
124             change = mCalendarChanges.get(id);
125         } else {
126             change = new Boolean[CHANGES_SIZE];
127             change[SELECTED_INDEX] = initialState[SELECTED_INDEX];
128             change[SYNCED_INDEX] = initialState[SYNCED_INDEX];
129             mCalendarChanges.put(id, change);
130         }
131
132         if (change[SELECTED_INDEX]) {
133             change[SELECTED_INDEX] = false;
134             status = syncedNotVisible;
135         }
136         else if (change[SYNCED_INDEX]) {
137             change[SYNCED_INDEX] = false;
138             status = notSyncedNotVisible;
139         }
140         else
141         {
142             change[SYNCED_INDEX] = true;
143             change[SELECTED_INDEX] = true;
144             status = syncedVisible;
145         }
146         setText(view, R.id.status, status);
147         if (change[SELECTED_INDEX] == initialState[SELECTED_INDEX] &&
148                 change[SYNCED_INDEX] == initialState[SYNCED_INDEX]) {
149             mCalendarChanges.remove(id);
150         }
151     }
152
153     public SelectCalendarsAdapter(Context context, Cursor cursor, SelectCalendarsActivity act) {
154         super(cursor, context);
155         syncedVisible = context.getString(R.string.synced_visible);
156         syncedNotVisible = context.getString(R.string.synced_not_visible);
157         notSyncedNotVisible = context.getString(R.string.not_synced_not_visible);
158
159         mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
160         mResolver = context.getContentResolver();
161         mActivity = act;
162         if (mCalendarsUpdater == null) {
163             mCalendarsUpdater = new AsyncCalendarsUpdater(mResolver);
164         }
165         if(cursor.getCount() == 0) {
166             //Should never happen since Calendar requires an account exist to use it.
167             Log.e(TAG, "SelectCalendarsAdapter: No accounts were returned!");
168         }
169         //Collect proper description for account types
170         mAuthDescs = AccountManager.get(context).getAuthenticatorTypes();
171         for (int i = 0; i < mAuthDescs.length; i++) {
172             mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]);
173         }
174     }
175
176     /*
177      * Write back the changes that have been made. The sync code will pick up any changes and
178      * do updates on its own.
179      */
180     public void doSaveAction() {
181         //Cancel the previous operation
182         mCalendarsUpdater.cancelOperation(mUpdateToken);
183         mUpdateToken++;
184
185         Iterator<Long> changeKeys = mCalendarChanges.keySet().iterator();
186         while (changeKeys.hasNext()) {
187             long id = changeKeys.next();
188             Boolean[] change = mCalendarChanges.get(id);
189             int newSelected = change[SELECTED_INDEX] ? 1 : 0;
190             int newSynced = change[SYNCED_INDEX] ? 1 : 0;
191
192             Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
193             ContentValues values = new ContentValues();
194             values.put(Calendars.SELECTED, newSelected);
195             values.put(Calendars.SYNC_EVENTS, newSynced);
196             mCalendarsUpdater.startUpdate(mUpdateToken, id, uri, values, null, null);
197         }
198     }
199
200     private static void setText(View view, int id, String text) {
201         if (TextUtils.isEmpty(text)) {
202             return;
203         }
204         TextView textView = (TextView) view.findViewById(id);
205         textView.setText(text);
206     }
207
208     /**
209      * Gets the label associated with a particular account type. If none found, return null.
210      * @param accountType the type of account
211      * @return a CharSequence for the label or null if one cannot be found.
212      */
213     protected CharSequence getLabelForType(final String accountType) {
214         CharSequence label = null;
215         if (mTypeToAuthDescription.containsKey(accountType)) {
216              try {
217                  AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
218                  Context authContext = mActivity.createPackageContext(desc.packageName, 0);
219                  label = authContext.getResources().getText(desc.labelId);
220              } catch (PackageManager.NameNotFoundException e) {
221                  Log.w(TAG, "No label for account type " + ", type " + accountType);
222              }
223         }
224         return label;
225     }
226
227     private Drawable getColorChip(int color) {
228
229         /*
230          * We want the color chip to have a nice gradient using
231          * the color of the calendar. To do this we use a GradientDrawable.
232          * The color supplied has an alpha of FF so we first do:
233          * color & 0x00FFFFFF
234          * to clear the alpha. Then we add our alpha to it.
235          * We use 3 colors to get a step effect where it starts off very
236          * light and quickly becomes dark and then a slow transition to
237          * be even darker.
238          */
239         color &= CLEAR_ALPHA_MASK;
240         int startColor = color | HIGH_ALPHA;
241         int middleColor = color | MED_ALPHA;
242         int endColor = color | LOW_ALPHA;
243         int[] colors = new int[] {startColor, middleColor, endColor};
244         GradientDrawable d = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, colors);
245         d.setCornerRadii(CORNERS);
246         return d;
247     }
248
249     @Override
250     protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
251         String account = cursor.getString(ACCOUNT_COLUMN);
252         String status = notSyncedNotVisible;
253         int state = 2;
254         int position = cursor.getPosition();
255         long id = cursor.getLong(ID_COLUMN);
256
257         // First see if the user has already changed the state of this calendar
258         Boolean[] initialState = mCalendarChanges.get(id);
259         // if not just grab the initial state
260         if (initialState == null) {
261             initialState = mCalendarInitialStates.get(id);
262         }
263         // and create a new initial state if we've never seen this calendar before.
264         if(initialState == null) {
265             initialState = new Boolean[CHANGES_SIZE];
266             initialState[SELECTED_INDEX] = cursor.getInt(SELECTED_COLUMN) == 1;
267             initialState[SYNCED_INDEX] = cursor.getInt(SYNCED_COLUMN) == 1;
268             mCalendarInitialStates.put(id, initialState);
269         }
270
271         if(initialState[SYNCED_INDEX]) {
272             if(initialState[SELECTED_INDEX]) {
273                 status = syncedVisible;
274                 state = 0;
275             } else {
276                 status = syncedNotVisible;
277                 state = 1;
278             }
279         }
280
281         view.findViewById(R.id.color)
282             .setBackgroundDrawable(getColorChip(cursor.getInt(COLOR_COLUMN)));
283         setText(view, R.id.calendar, cursor.getString(NAME_COLUMN));
284         setText(view, R.id.status, status);
285         MultiStateButton button = (MultiStateButton) view.findViewById(R.id.multiStateButton);
286
287         //Set up the listeners so a click on the button will change the state.
288         //The view already uses the onChildClick method in the activity.
289         button.setTag(view);
290         view.setTag(id);
291         button.setOnClickListener(this);
292         button.setButtonResources(SYNC_VIS_BUTTON_RES);
293         button.setState(state);
294     }
295
296     @Override
297     protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
298         int accountColumn = cursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT);
299         int accountTypeColumn = cursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT_TYPE);
300         String account = cursor.getString(accountColumn);
301         String accountType = cursor.getString(accountTypeColumn);
302         setText(view, R.id.account, account);
303         setText(view, R.id.account_type, getLabelForType(accountType).toString());
304     }
305
306     @Override
307     protected Cursor getChildrenCursor(Cursor groupCursor) {
308         int accountColumn = groupCursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT);
309         String account = groupCursor.getString(accountColumn);
310         //Get all the calendars for just this account.
311         //TODO Move managedQuery into a background thread in CP2
312         Cursor childCursor = mActivity.managedQuery(Calendars.CONTENT_URI, PROJECTION,
313                 Calendars._SYNC_ACCOUNT + "=\"" + account + "\"" /*Selection*/,
314                 null /* selectionArgs */,
315                 Calendars.DISPLAY_NAME);
316         return childCursor;
317     }
318
319     @Override
320     protected View newChildView(Context context, Cursor cursor, boolean isLastChild,
321             ViewGroup parent) {
322         return mInflater.inflate(R.layout.calendar_item, parent, false);
323     }
324
325     @Override
326     protected View newGroupView(Context context, Cursor cursor, boolean isExpanded,
327             ViewGroup parent) {
328         return mInflater.inflate(R.layout.account_item, parent, false);
329     }
330 }