12aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan/*
22aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan * Copyright (C) 2011 The Android Open Source Project
32aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan *
42aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan * Licensed under the Apache License, Version 2.0 (the "License");
52aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan * you may not use this file except in compliance with the License.
62aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan * You may obtain a copy of the License at
72aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan *
82aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan *      http://www.apache.org/licenses/LICENSE-2.0
92aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan *
102aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan * Unless required by applicable law or agreed to in writing, software
112aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan * distributed under the License is distributed on an "AS IS" BASIS,
122aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan * See the License for the specific language governing permissions and
142aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan * limitations under the License.
152aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan */
162aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
172aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanpackage com.android.calendar.selectcalendars;
182aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
192aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport com.android.calendar.R;
202aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport com.android.calendar.Utils;
212aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
222aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.accounts.AccountManager;
232aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.accounts.AuthenticatorDescription;
242aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.content.AsyncQueryHandler;
252aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.content.ContentResolver;
262aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.content.ContentUris;
272aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.content.ContentValues;
282aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.content.Context;
292aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.content.pm.PackageManager;
302aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.database.Cursor;
312aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.database.MatrixCursor;
322aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.net.Uri;
332aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.provider.CalendarContract.Calendars;
342aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.text.TextUtils;
352aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.util.Log;
362aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.view.LayoutInflater;
372aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.view.View;
382aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.view.ViewGroup;
392aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.widget.CheckBox;
402aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.widget.CursorTreeAdapter;
412aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport android.widget.TextView;
422aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
432aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport java.util.HashMap;
442aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport java.util.Iterator;
452aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanimport java.util.Map;
462aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
472aeb8d988aa4b65d3402374832613ab977e009dcMichael Chanpublic class SelectSyncedCalendarsMultiAccountAdapter extends CursorTreeAdapter implements
482aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        View.OnClickListener {
492aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
502aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final String TAG = "Calendar";
512aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
522aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final String IS_PRIMARY = "\"primary\"";
532aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final String CALENDARS_ORDERBY = IS_PRIMARY + " DESC,"
542aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            + Calendars.CALENDAR_DISPLAY_NAME + " COLLATE NOCASE";
552aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final String ACCOUNT_SELECTION = Calendars.ACCOUNT_NAME + "=?"
562aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            + " AND " + Calendars.ACCOUNT_TYPE + "=?";
572aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
582aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private final LayoutInflater mInflater;
592aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private final ContentResolver mResolver;
602aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private final SelectSyncedCalendarsMultiAccountActivity mActivity;
612aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private final View mView;
622aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private final static Runnable mStopRefreshing = new Runnable() {
632aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        public void run() {
642aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mRefresh = false;
652aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
662aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    };
672aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private Map<String, AuthenticatorDescription> mTypeToAuthDescription
682aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        = new HashMap<String, AuthenticatorDescription>();
692aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    protected AuthenticatorDescription[] mAuthDescs;
702aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
712aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    // These track changes to the synced state of calendars
722aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private Map<Long, Boolean> mCalendarChanges
732aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        = new HashMap<Long, Boolean>();
742aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private Map<Long, Boolean> mCalendarInitialStates
752aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        = new HashMap<Long, Boolean>();
762aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
772aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    // This is for keeping MatrixCursor copies so that we can requery in the background.
782aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static Map<String, Cursor> mChildrenCursors
792aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        = new HashMap<String, Cursor>();
802aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
812aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static AsyncCalendarsUpdater mCalendarsUpdater;
822aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    // This is to keep our update tokens separate from other tokens. Since we cancel old updates
832aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    // when a new update comes in, we'd like to leave a token space that won't be canceled.
842aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int MIN_UPDATE_TOKEN = 1000;
852aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static int mUpdateToken = MIN_UPDATE_TOKEN;
862aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    // How long to wait between requeries of the calendars to see if anything has changed.
872aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int REFRESH_DELAY = 5000;
882aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    // How long to keep refreshing for
892aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int REFRESH_DURATION = 60000;
902aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static boolean mRefresh = true;
912aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
922aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static String mSyncedText;
932aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static String mNotSyncedText;
942aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
952aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    // This is to keep track of whether or not multiple calendars have the same display name
962aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static HashMap<String, Boolean> mIsDuplicateName = new HashMap<String, Boolean>();
972aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
982aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final String[] PROJECTION = new String[] {
992aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan      Calendars._ID,
1002aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan      Calendars.ACCOUNT_NAME,
1012aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan      Calendars.OWNER_ACCOUNT,
1022aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan      Calendars.CALENDAR_DISPLAY_NAME,
1032aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan      Calendars.CALENDAR_COLOR,
1042aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan      Calendars.VISIBLE,
1052aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan      Calendars.SYNC_EVENTS,
1062aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan      "(" + Calendars.ACCOUNT_NAME + "=" + Calendars.OWNER_ACCOUNT + ") AS " + IS_PRIMARY,
1072aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    };
1082aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    //Keep these in sync with the projection
1092aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int ID_COLUMN = 0;
1102aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int ACCOUNT_COLUMN = 1;
1112aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int OWNER_COLUMN = 2;
1122aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int NAME_COLUMN = 3;
1132aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int COLOR_COLUMN = 4;
1142aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int SELECTED_COLUMN = 5;
1152aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int SYNCED_COLUMN = 6;
1162aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int PRIMARY_COLUMN = 7;
1172aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
1182aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int TAG_ID_CALENDAR_ID = R.id.calendar;
1192aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static final int TAG_ID_SYNC_CHECKBOX = R.id.sync;
1202aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
1212aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private class AsyncCalendarsUpdater extends AsyncQueryHandler {
1222aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
1232aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        public AsyncCalendarsUpdater(ContentResolver cr) {
1242aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            super(cr);
1252aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
1262aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
1272aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        @Override
1282aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
1292aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            if(cursor == null) {
1302aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                return;
1312aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            }
1322aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
1332aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            Cursor currentCursor = mChildrenCursors.get(cookie);
1342aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            // Check if the new cursor has the same content as our old cursor
1352aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            if (currentCursor != null) {
1362aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                if (Utils.compareCursors(currentCursor, cursor)) {
1372aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                    cursor.close();
1382aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                    return;
1392aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                }
1402aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            }
1412aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            // If not then make a new matrix cursor for our Map
1422aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            MatrixCursor newCursor = Utils.matrixCursorFromCursor(cursor);
1432aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            cursor.close();
1442aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            // And update our list of duplicated names
1452aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            Utils.checkForDuplicateNames(mIsDuplicateName, newCursor, NAME_COLUMN);
1462aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
1472aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mChildrenCursors.put((String)cookie, newCursor);
1482aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            try {
1492aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                setChildrenCursor(token, newCursor);
1502aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                mActivity.startManagingCursor(newCursor);
1512aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            } catch (NullPointerException e) {
1522aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                Log.w(TAG, "Adapter expired, try again on the next query: " + e);
1532aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            }
1542aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            // Clean up our old cursor if we had one. We have to do this after setting the new
1552aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            // cursor so that our view doesn't throw on an invalid cursor.
1562aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            if (currentCursor != null) {
1572aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                mActivity.stopManagingCursor(currentCursor);
1582aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                currentCursor.close();
1592aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            }
1602aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
1612aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
1622aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
1632aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    /**
1642aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     * Method for changing the sync state when a calendar's button is pressed.
1652aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     *
1662aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     * This gets called when the CheckBox for a calendar is clicked. It toggles
1672aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     * the sync state for the associated calendar and saves a change of state to
1682aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     * a hashmap. It also compares against the original value and removes any
1692aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     * changes from the hashmap if this is back at its initial state.
1702aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     */
1712aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    public void onClick(View v) {
1722aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        long id = (Long) v.getTag(TAG_ID_CALENDAR_ID);
1732aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        boolean newState;
1742aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        boolean initialState = mCalendarInitialStates.get(id);
1752aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        if (mCalendarChanges.containsKey(id)) {
1762aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            // Negate to reflect the click
1772aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            newState = !mCalendarChanges.get(id);
1782aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        } else {
1792aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            // Negate to reflect the click
1802aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            newState = !initialState;
1812aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
1822aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
1832aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        if (newState == initialState) {
1842aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mCalendarChanges.remove(id);
1852aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        } else {
1862aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mCalendarChanges.put(id, newState);
1872aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
1882aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
1892aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        ((CheckBox) v.getTag(TAG_ID_SYNC_CHECKBOX)).setChecked(newState);
1902aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        setText(v, R.id.status, newState ? mSyncedText : mNotSyncedText);
1912aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
1922aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
1932aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    public SelectSyncedCalendarsMultiAccountAdapter(Context context, Cursor acctsCursor,
1942aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            SelectSyncedCalendarsMultiAccountActivity act) {
1952aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        super(acctsCursor, context);
1962aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mSyncedText = context.getString(R.string.synced);
1972aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mNotSyncedText = context.getString(R.string.not_synced);
1982aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
1992aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2002aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mResolver = context.getContentResolver();
2012aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mActivity = act;
2022aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        if (mCalendarsUpdater == null) {
2032aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mCalendarsUpdater = new AsyncCalendarsUpdater(mResolver);
2042aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
2052aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
2066fb869698103a0f897537fa7f4ebabe714db9d37Michael Chan        if (acctsCursor == null || acctsCursor.getCount() == 0) {
2076fb869698103a0f897537fa7f4ebabe714db9d37Michael Chan            Log.i(TAG, "SelectCalendarsAdapter: No accounts were returned!");
2082aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
2092aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        // Collect proper description for account types
2102aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mAuthDescs = AccountManager.get(context).getAuthenticatorTypes();
2112aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        for (int i = 0; i < mAuthDescs.length; i++) {
2122aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]);
2132aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
2142aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mView = mActivity.getExpandableListView();
2152aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mRefresh = true;
2162aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
2172aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
2182aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    public void startRefreshStopDelay() {
2192aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mRefresh = true;
2202aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mView.postDelayed(mStopRefreshing, REFRESH_DURATION);
2212aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
2222aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
2232aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    public void cancelRefreshStopDelay() {
2242aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mView.removeCallbacks(mStopRefreshing);
2252aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
2262aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
2272aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    /*
2282aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     * Write back the changes that have been made. The sync code will pick up any changes and
2292aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     * do updates on its own.
2302aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     */
2312aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    public void doSaveAction() {
2322aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        // Cancel the previous operation
2332aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mCalendarsUpdater.cancelOperation(mUpdateToken);
2342aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        mUpdateToken++;
2352aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        // This is to allow us to do queries and updates with the same AsyncQueryHandler without
2362aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        // accidently canceling queries.
2372aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        if(mUpdateToken < MIN_UPDATE_TOKEN) {
2382aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mUpdateToken = MIN_UPDATE_TOKEN;
2392aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
2402aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
2412aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        Iterator<Long> changeKeys = mCalendarChanges.keySet().iterator();
2422aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        while (changeKeys.hasNext()) {
2432aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            long id = changeKeys.next();
2442aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            boolean newSynced = mCalendarChanges.get(id);
2452aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
2462aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
2472aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            ContentValues values = new ContentValues();
2482aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            values.put(Calendars.VISIBLE, newSynced ? 1 : 0);
2492aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            values.put(Calendars.SYNC_EVENTS, newSynced ? 1 : 0);
2502aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mCalendarsUpdater.startUpdate(mUpdateToken, id, uri, values, null, null);
2512aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
2522aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
2532aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
2542aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private static void setText(View view, int id, String text) {
2552aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        if (TextUtils.isEmpty(text)) {
2562aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            return;
2572aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
2582aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        TextView textView = (TextView) view.findViewById(id);
2592aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        textView.setText(text);
2602aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
2612aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
2622aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    /**
2632aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     * Gets the label associated with a particular account type. If none found, return null.
2642aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     * @param accountType the type of account
2652aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     * @return a CharSequence for the label or null if one cannot be found.
2662aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan     */
2672aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    protected CharSequence getLabelForType(final String accountType) {
2682aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        CharSequence label = null;
2692aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        if (mTypeToAuthDescription.containsKey(accountType)) {
2702aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan             try {
2712aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                 AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
2722aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                 Context authContext = mActivity.createPackageContext(desc.packageName, 0);
2732aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                 label = authContext.getResources().getText(desc.labelId);
2742aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan             } catch (PackageManager.NameNotFoundException e) {
2752aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                 Log.w(TAG, "No label for account type " + ", type " + accountType);
2762aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan             }
2772aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
2782aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        return label;
2792aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
2802aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
2812aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    @Override
2822aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
2832aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        view.findViewById(R.id.color).setBackgroundColor(cursor.getInt(COLOR_COLUMN));
2842aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        String name = cursor.getString(NAME_COLUMN);
2852aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        String owner = cursor.getString(OWNER_COLUMN);
2862aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        if (mIsDuplicateName.containsKey(name) && mIsDuplicateName.get(name) &&
2872aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                !name.equalsIgnoreCase(owner)) {
2882aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            name = new StringBuilder(name)
2892aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                    .append(Utils.OPEN_EMAIL_MARKER)
2902aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                    .append(owner)
2912aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                    .append(Utils.CLOSE_EMAIL_MARKER)
2922aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                    .toString();
2932aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
2942aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        setText(view, R.id.calendar, name);
2952aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
2962aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        // First see if the user has already changed the state of this calendar
2972aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        long id = cursor.getLong(ID_COLUMN);
2982aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        Boolean sync = mCalendarChanges.get(id);
2992aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        if (sync == null) {
3002aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            sync = cursor.getInt(SYNCED_COLUMN) == 1;
3012aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mCalendarInitialStates.put(id, sync);
3022aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
3032aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
3042aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        CheckBox button = (CheckBox) view.findViewById(R.id.sync);
3052aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        button.setChecked(sync);
3062aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        setText(view, R.id.status, sync ? mSyncedText : mNotSyncedText);
3072aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
3082aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        view.setTag(TAG_ID_CALENDAR_ID, id);
3092aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        view.setTag(TAG_ID_SYNC_CHECKBOX, button);
3102aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        view.setOnClickListener(this);
3112aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
3122aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
3132aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    @Override
3142aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
3152aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        int accountColumn = cursor.getColumnIndexOrThrow(Calendars.ACCOUNT_NAME);
3162aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        int accountTypeColumn = cursor.getColumnIndexOrThrow(Calendars.ACCOUNT_TYPE);
3172aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        String account = cursor.getString(accountColumn);
3182aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        String accountType = cursor.getString(accountTypeColumn);
319953fb5d952c793a2514fd48abef8d8bcff7661dcSara Ting        CharSequence accountLabel = getLabelForType(accountType);
3202aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        setText(view, R.id.account, account);
321953fb5d952c793a2514fd48abef8d8bcff7661dcSara Ting        if (accountLabel != null) {
322953fb5d952c793a2514fd48abef8d8bcff7661dcSara Ting            setText(view, R.id.account_type, accountLabel.toString());
323953fb5d952c793a2514fd48abef8d8bcff7661dcSara Ting        }
3242aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
3252aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
3262aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    @Override
3272aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    protected Cursor getChildrenCursor(Cursor groupCursor) {
3282aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        int accountColumn = groupCursor.getColumnIndexOrThrow(Calendars.ACCOUNT_NAME);
3292aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        int accountTypeColumn = groupCursor.getColumnIndexOrThrow(Calendars.ACCOUNT_TYPE);
3302aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        String account = groupCursor.getString(accountColumn);
3312aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        String accountType = groupCursor.getString(accountTypeColumn);
3322aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        //Get all the calendars for just this account.
33365cf22bc9170c8aa6a26ef0bf91888a0ae1afaebMichael Chan        Cursor childCursor = mChildrenCursors.get(accountType + "#" + account);
3342aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        new RefreshCalendars(groupCursor.getPosition(), account, accountType).run();
3352aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        return childCursor;
3362aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
3372aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
3382aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    @Override
3392aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    protected View newChildView(Context context, Cursor cursor, boolean isLastChild,
3402aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            ViewGroup parent) {
3412aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        return mInflater.inflate(R.layout.calendar_sync_item, parent, false);
3422aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
3432aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
3442aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    @Override
3452aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    protected View newGroupView(Context context, Cursor cursor, boolean isExpanded,
3462aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            ViewGroup parent) {
3472aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        return mInflater.inflate(R.layout.account_item, parent, false);
3482aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
3492aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
3502aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    private class RefreshCalendars implements Runnable {
3512aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
3522aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        int mToken;
3532aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        String mAccount;
3542aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        String mAccountType;
3552aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
35665cf22bc9170c8aa6a26ef0bf91888a0ae1afaebMichael Chan        public RefreshCalendars(int token, String account, String accountType) {
3572aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mToken = token;
35865cf22bc9170c8aa6a26ef0bf91888a0ae1afaebMichael Chan            mAccount = account;
3592aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mAccountType = accountType;
3602aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
3612aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan
3622aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        public void run() {
3632aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mCalendarsUpdater.cancelOperation(mToken);
3642aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            // Set up a refresh for some point in the future if we haven't stopped updates yet
3652aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            if(mRefresh) {
3662aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                mView.postDelayed(new RefreshCalendars(mToken, mAccount, mAccountType),
3672aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                        REFRESH_DELAY);
3682aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            }
3692aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan            mCalendarsUpdater.startQuery(mToken,
37065cf22bc9170c8aa6a26ef0bf91888a0ae1afaebMichael Chan                    mAccountType + "#" + mAccount,
3712aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                    Calendars.CONTENT_URI, PROJECTION,
3722aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                    ACCOUNT_SELECTION,
3732aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                    new String[] { mAccount, mAccountType } /*selectionArgs*/,
3742aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan                    CALENDARS_ORDERBY);
3752aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan        }
3762aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan    }
3772aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan}
378