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
index a7ffc5c..3762362 100644 (file)
 
 package com.android.calendar;
 
-import java.util.HashMap;
-import java.util.Map;
-
 import android.accounts.AccountManager;
 import android.accounts.AuthenticatorDescription;
+import android.content.AsyncQueryHandler;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
-import android.database.sqlite.SQLiteStatement;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
+import android.database.MatrixCursor;
 import android.net.Uri;
 import android.provider.Calendar.Calendars;
 import android.text.TextUtils;
@@ -40,19 +36,15 @@ import android.view.ViewGroup;
 import android.widget.CursorTreeAdapter;
 import android.widget.TextView;
 
-public class SelectCalendarsAdapter extends CursorTreeAdapter {
-
-    private static final int CLEAR_ALPHA_MASK = 0x00FFFFFF;
-    private static final int HIGH_ALPHA = 255 << 24;
-    private static final int MED_ALPHA = 180 << 24;
-    private static final int LOW_ALPHA = 150 << 24;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 
-    /* The corner should be rounded on the top right and bottom right */
-    private static final float[] CORNERS = new float[] {0, 0, 5, 5, 5, 5, 0, 0};
+public class SelectCalendarsAdapter extends CursorTreeAdapter implements View.OnClickListener {
 
     private static final String TAG = "Calendar";
 
-    //TODO replace with final icon names
+    // The drawables used for the button to change the visible and sync states on a calendar
     private static final int[] SYNC_VIS_BUTTON_RES = new int[] {
         R.drawable.widget_show,
         R.drawable.widget_sync,
@@ -61,135 +53,185 @@ public class SelectCalendarsAdapter extends CursorTreeAdapter {
 
     private final LayoutInflater mInflater;
     private final ContentResolver mResolver;
-    private final ContentValues mValues = new ContentValues();
     private final SelectCalendarsActivity mActivity;
-    private SelectCalendarsChild mChildren[];
-    private Cursor tagCursor;
     private Map<String, AuthenticatorDescription> mTypeToAuthDescription
         = new HashMap<String, AuthenticatorDescription>();
     protected AuthenticatorDescription[] mAuthDescs;
 
+    // These track changes to the visible (selected) and synced state of calendars
+    private Map<Long, Boolean[]> mCalendarChanges
+        = new HashMap<Long, Boolean[]>();
+    private Map<Long, Boolean[]> mCalendarInitialStates
+        = new HashMap<Long, Boolean[]>();
+    private static final int SELECTED_INDEX = 0;
+    private static final int SYNCED_INDEX = 1;
+    private static final int CHANGES_SIZE = 2;
+
+    // This is for keeping MatrixCursor copies so that we can requery in the background.
+    private static Map<String, Cursor> mChildrenCursors
+        = new HashMap<String, Cursor>();
+
+    private static AsyncCalendarsUpdater mCalendarsUpdater;
+    // This is to keep our update tokens separate from other tokens. Since we cancel old updates
+    // when a new update comes in, we'd like to leave a token space that won't be canceled.
+    private static final int MIN_UPDATE_TOKEN = 1000;
+    private static int mUpdateToken = MIN_UPDATE_TOKEN;
+
     private static String syncedVisible;
     private static String syncedNotVisible;
     private static String notSyncedNotVisible;
 
+    // This is to keep track of whether or not multiple calendars have the same display name
+    private static HashMap<String, Boolean> mIsDuplicateName = new HashMap<String, Boolean>();
+
     private static final String[] PROJECTION = new String[] {
       Calendars._ID,
       Calendars._SYNC_ACCOUNT,
+      Calendars.OWNER_ACCOUNT,
       Calendars.DISPLAY_NAME,
       Calendars.COLOR,
       Calendars.SELECTED,
       Calendars.SYNC_EVENTS
     };
-
     //Keep these in sync with the projection
     private static final int ID_COLUMN = 0;
     private static final int ACCOUNT_COLUMN = 1;
-    private static final int NAME_COLUMN = 2;
-    private static final int COLOR_COLUMN = 3;
-    private static final int SELECTED_COLUMN = 4;
-    private static final int SYNCED_COLUMN = 5;
+    private static final int OWNER_COLUMN = 2;
+    private static final int NAME_COLUMN = 3;
+    private static final int COLOR_COLUMN = 4;
+    private static final int SELECTED_COLUMN = 5;
+    private static final int SYNCED_COLUMN = 6;
 
-    /**
-     * Data structure for holding all the information about a single account's calendars.
-     *
-     */
-    private class SelectCalendarsChild {
-        private String mAccount;
-        private Boolean mIsVisible[] = null;
-        private Boolean mIsSynced[] = null;
-
-        SelectCalendarsChild(String account, Cursor cursor) {
-            mAccount = account;
-            initIsStatusArrays(cursor.getCount());
-        }
+    private class AsyncCalendarsUpdater extends AsyncQueryHandler {
 
-        private void initIsStatusArrays(int cursorCount) {
-            mIsVisible = new Boolean[cursorCount];
-            mIsSynced = new Boolean[cursorCount];
+        public AsyncCalendarsUpdater(ContentResolver cr) {
+            super(cr);
         }
 
-        private void initIsStatusArrayElement(Cursor cursor) {
-            int position = cursor.getPosition();
-            if(cursor.getInt(SYNCED_COLUMN) == 1) {
-                mIsSynced[position] = true;
-                if(cursor.getInt(SELECTED_COLUMN) == 1) {
-                    mIsVisible[position] = true;
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            if(cursor == null) {
+                return;
+            }
+
+            Cursor currentCursor = mChildrenCursors.get(cookie);
+            // Check if the new cursor has the same content as our old cursor
+            if (currentCursor != null) {
+                if (compareCursors(currentCursor, cursor)) {
+                    cursor.close();
+                    return;
                 } else {
-                    mIsVisible[position] = false;
+                    mActivity.stopManagingCursor(currentCursor);
+                    currentCursor.close();
+                    mChildrenCursors.remove(cookie);
                 }
-            } else {
-                mIsSynced[position] = false;
-                mIsVisible[position] = false;
             }
+            // If not then make a new matrix cursor for our Map
+            MatrixCursor newCursor = matrixCursorFromCursor(cursor);
+            // And update our list of duplicated names
+            Utils.checkForDuplicateNames(mIsDuplicateName, cursor, NAME_COLUMN);
+
+            mChildrenCursors.put((String)cookie, newCursor);
+            try {
+                setChildrenCursor(token, newCursor);
+                mActivity.startManagingCursor(newCursor);
+            } catch (NullPointerException e) {
+                Log.w(TAG, "Adapter expired, try again on the next query: " + e.getMessage());
+            }
+            cursor.close();
         }
 
-        public String getAccount() {
-            return mAccount;
-        }
+        /**
+         * Compares two cursors to see if they contain the same data.
+         *
+         * @return Returns true of the cursors contain the same data and are not null, false
+         * otherwise
+         */
+        private boolean compareCursors(Cursor c1, Cursor c2) {
+            if(c1 == null || c2 == null) {
+                return false;
+            }
 
-        public Boolean[] getSelected() {
-            return mIsVisible;
-        }
+            int numColumns = c1.getColumnCount();
+            if (numColumns != c2.getColumnCount()) {
+                return false;
+            }
 
-        public Boolean[] getSynced() {
-            return mIsSynced;
-        }
+            if (c1.getCount() != c2.getCount()) {
+                return false;
+            }
 
-        public void setSelected(boolean value, int position) {
-            mIsVisible[position] = value;
-        }
+            c1.moveToPosition(-1);
+            c2.moveToPosition(-1);
+            while(c1.moveToNext() && c2.moveToNext()) {
+                for(int i = 0; i < numColumns; i++) {
+                    if(!c1.getString(i).equals(c2.getString(i))) {
+                        return false;
+                    }
+                }
+            }
 
-        public void setSynced(boolean value, int position) {
-            mIsSynced[position] = value;
+            return true;
         }
-    }
 
-    private SelectCalendarsChild findChildByAccount(String act) {
-        for(int i = 0; i < mChildren.length; i++) {
-            if(act.equals(mChildren[i].getAccount())) {
-                return mChildren[i];
+        private MatrixCursor matrixCursorFromCursor(Cursor cursor) {
+            MatrixCursor newCursor = new MatrixCursor(cursor.getColumnNames());
+            int numColumns = cursor.getColumnCount();
+            int count = cursor.getCount();
+            String data[] = new String[numColumns];
+            cursor.moveToPosition(-1);
+            while(cursor.moveToNext()) {
+                for(int i = 0; i < numColumns; i++) {
+                    data[i] = cursor.getString(i);
+                }
+                newCursor.addRow(data);
             }
+            return newCursor;
         }
-        return null;
     }
 
-    private class ButtonListener implements View.OnClickListener {
-        private final long mCalendarId;
-        private final int mPosition;
-        private final String mAccount;
-        private final View mView;
-
-        private ButtonListener(long calendarId, int position, String account, View view) {
-            mPosition = position;
-            mCalendarId = calendarId;
-            mAccount = account;
-            mView = view;
+    /**
+     * Method for changing the sync/vis state when a calendar's button is pressed.
+     *
+     * This gets called when the MultiStateButton for a calendar is clicked. It cycles the sync/vis
+     * state for the associated calendar and saves a change of state to a hashmap. It also compares
+     * against the original value and removes any changes from the hashmap if this is back
+     * at its initial state.
+     */
+    public void onClick(View v) {
+        View view = (View)v.getTag();
+        long id = (Long)view.getTag();
+        Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
+        String status = syncedNotVisible;
+        Boolean[] change;
+        Boolean[] initialState = mCalendarInitialStates.get(id);
+        if (mCalendarChanges.containsKey(id)) {
+            change = mCalendarChanges.get(id);
+        } else {
+            change = new Boolean[CHANGES_SIZE];
+            change[SELECTED_INDEX] = initialState[SELECTED_INDEX];
+            change[SYNCED_INDEX] = initialState[SYNCED_INDEX];
+            mCalendarChanges.put(id, change);
         }
 
-        public void onClick(View buttonView) {
-            Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, mCalendarId);
-            String status = syncedNotVisible;
-            mValues.clear();
-            SelectCalendarsChild child = findChildByAccount(mAccount);
-            Boolean isSelected[] = child.getSelected();
-            Boolean isSynced[] = child.getSynced();
-
-            if(isSelected[mPosition]) {
-                isSelected[mPosition] = false;
-                status = syncedNotVisible;
-            }
-            else if(isSynced[mPosition]) {
-                isSynced[mPosition] = false;
-                status = notSyncedNotVisible;
-            }
-            else
-            {
-                isSynced[mPosition] = true;
-                isSelected[mPosition] = true;
-                status = syncedVisible;
-            }
-            setText(mView, R.id.status, status);
+        if (change[SELECTED_INDEX]) {
+            change[SELECTED_INDEX] = false;
+            status = syncedNotVisible;
+        }
+        else if (change[SYNCED_INDEX]) {
+            change[SYNCED_INDEX] = false;
+            status = notSyncedNotVisible;
+        }
+        else
+        {
+            change[SYNCED_INDEX] = true;
+            change[SELECTED_INDEX] = true;
+            status = syncedVisible;
+        }
+        setText(view, R.id.status, status);
+        if (change[SELECTED_INDEX] == initialState[SELECTED_INDEX] &&
+                change[SYNCED_INDEX] == initialState[SYNCED_INDEX]) {
+            mCalendarChanges.remove(id);
         }
     }
 
@@ -202,12 +244,13 @@ public class SelectCalendarsAdapter extends CursorTreeAdapter {
         mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         mResolver = context.getContentResolver();
         mActivity = act;
+        if (mCalendarsUpdater == null) {
+            mCalendarsUpdater = new AsyncCalendarsUpdater(mResolver);
+        }
         if(cursor.getCount() == 0) {
             //Should never happen since Calendar requires an account exist to use it.
             Log.e(TAG, "SelectCalendarsAdapter: No accounts were returned!");
         }
-        updateChildren(cursor);
-        tagCursor = cursor;
         //Collect proper description for account types
         mAuthDescs = AccountManager.get(context).getAuthenticatorTypes();
         for (int i = 0; i < mAuthDescs.length; i++) {
@@ -215,64 +258,30 @@ public class SelectCalendarsAdapter extends CursorTreeAdapter {
         }
     }
 
-    /**
-     * Updates the mChildren array at startup or if the number of accounts changed.
-     *
-     * @param cursor A cursor containing the account names.
-     */
-    private void updateChildren(Cursor cursor) {
-        int accountColumn = cursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT);
-        mChildren = new SelectCalendarsChild[cursor.getCount()];
-        cursor.moveToPosition(-1);
-        int position = -1;
-        String[] selectionArgs = new String[1];
-        while(cursor.moveToNext()) {
-            position++;
-            selectionArgs[0] = cursor.getString(accountColumn);
-            //TODO Move managedQuery into a background thread
-            Cursor childCursor = mActivity.managedQuery(Calendars.CONTENT_URI, PROJECTION,
-                    Calendars._SYNC_ACCOUNT + "=?"/*Selection*/,
-                    selectionArgs,
-                    Calendars.DISPLAY_NAME);
-            mChildren[position] = new SelectCalendarsChild(selectionArgs[0], childCursor);
-        }
-    }
-
     /*
      * Write back the changes that have been made. The sync code will pick up any changes and
      * do updates on its own.
      */
     public void doSaveAction() {
-        //If we never got a cursor we shouldn't do anything.
-        if(tagCursor == null) return;
-        //Start the cursor at beginning and make sure it's not empty
-        tagCursor.moveToPosition(-1);
-        while(tagCursor.moveToNext()) {
-            Cursor childCursor = getChildrenCursor(tagCursor);
-            childCursor.moveToPosition(-1);
-            SelectCalendarsChild child = mChildren[tagCursor.getPosition()];
-            Boolean isSelected[] = child.getSelected();
-            Boolean isSynced[] = child.getSynced();
-            //Go through each account's calendars and update the sync and selected settings
-            while(childCursor.moveToNext()) {
-                int position = childCursor.getPosition();
-                int selected = childCursor.getInt(SELECTED_COLUMN);
-                int synced = childCursor.getInt(SYNCED_COLUMN);
-                int id = childCursor.getInt(ID_COLUMN);
-                //If we never saw this calendar on the screen we can ignore it.
-                if(isSelected[position] != null && isSynced[position] != null) {
-                    int newSelected = isSelected[position] ? 1 : 0;
-                    int newSynced = isSynced[position] ? 1 : 0;
-                    //likewise, if nothing changed we can ignore it.
-                    if(newSelected != selected || newSynced != synced) {
-                        Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
-                        mValues.clear();
-                        mValues.put(Calendars.SELECTED, newSelected);
-                        mValues.put(Calendars.SYNC_EVENTS, newSynced);
-                        mResolver.update(uri, mValues, null, null);
-                    }
-                }
-            }
+        // Cancel the previous operation
+        mCalendarsUpdater.cancelOperation(mUpdateToken);
+        mUpdateToken++;
+        // This is to allow us to do queries and updates with the same AsyncQueryHandler without
+        // accidently canceling queries.
+        if(mUpdateToken < MIN_UPDATE_TOKEN) mUpdateToken = MIN_UPDATE_TOKEN;
+
+        Iterator<Long> changeKeys = mCalendarChanges.keySet().iterator();
+        while (changeKeys.hasNext()) {
+            long id = changeKeys.next();
+            Boolean[] change = mCalendarChanges.get(id);
+            int newSelected = change[SELECTED_INDEX] ? 1 : 0;
+            int newSynced = change[SYNCED_INDEX] ? 1 : 0;
+
+            Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
+            ContentValues values = new ContentValues();
+            values.put(Calendars.SELECTED, newSelected);
+            values.put(Calendars.SYNC_EVENTS, newSynced);
+            mCalendarsUpdater.startUpdate(mUpdateToken, id, uri, values, null, null);
         }
     }
 
@@ -293,8 +302,7 @@ public class SelectCalendarsAdapter extends CursorTreeAdapter {
         CharSequence label = null;
         if (mTypeToAuthDescription.containsKey(accountType)) {
              try {
-                 AuthenticatorDescription desc = (AuthenticatorDescription)
-                         mTypeToAuthDescription.get(accountType);
+                 AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
                  Context authContext = mActivity.createPackageContext(desc.packageName, 0);
                  label = authContext.getResources().getText(desc.labelId);
              } catch (PackageManager.NameNotFoundException e) {
@@ -304,54 +312,30 @@ public class SelectCalendarsAdapter extends CursorTreeAdapter {
         return label;
     }
 
-    private Drawable getColorChip(int color) {
-
-        /*
-         * We want the color chip to have a nice gradient using
-         * the color of the calendar. To do this we use a GradientDrawable.
-         * The color supplied has an alpha of FF so we first do:
-         * color & 0x00FFFFFF
-         * to clear the alpha. Then we add our alpha to it.
-         * We use 3 colors to get a step effect where it starts off very
-         * light and quickly becomes dark and then a slow transition to
-         * be even darker.
-         */
-        color &= CLEAR_ALPHA_MASK;
-        int startColor = color | HIGH_ALPHA;
-        int middleColor = color | MED_ALPHA;
-        int endColor = color | LOW_ALPHA;
-        int[] colors = new int[] {startColor, middleColor, endColor};
-        GradientDrawable d = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, colors);
-        d.setCornerRadii(CORNERS);
-        return d;
-    }
-
     @Override
     protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
         String account = cursor.getString(ACCOUNT_COLUMN);
         String status = notSyncedNotVisible;
         int state = 2;
         int position = cursor.getPosition();
+        long id = cursor.getLong(ID_COLUMN);
 
-        //Update our internal tracking if it hasn't been set before
-        SelectCalendarsChild child = findChildByAccount(account);
-        if(child == null) {
-            Log.e(TAG, "bindChildView:Tried to load an account we don't know about.");
+        // First see if the user has already changed the state of this calendar
+        Boolean[] initialState = mCalendarChanges.get(id);
+        // if not just grab the initial state
+        if (initialState == null) {
+            initialState = mCalendarInitialStates.get(id);
         }
-        Boolean isSelected[] = child.getSelected();
-        Boolean isSynced[] = child.getSynced();
-        // Update the array length if a new calendar has been added
-        //TODO add code to updateIsStatusArrays to check if valid data already exists and keep it
-        int cursorCount = cursor.getCount();
-        if (cursorCount != isSelected.length) {
-            child.initIsStatusArrays(cursorCount);
+        // and create a new initial state if we've never seen this calendar before.
+        if(initialState == null) {
+            initialState = new Boolean[CHANGES_SIZE];
+            initialState[SELECTED_INDEX] = cursor.getInt(SELECTED_COLUMN) == 1;
+            initialState[SYNCED_INDEX] = cursor.getInt(SYNCED_COLUMN) == 1;
+            mCalendarInitialStates.put(id, initialState);
         }
-        //and set the initial value if we need to
-        if(isSelected[position] == null || isSynced[position] == null) {
-            child.initIsStatusArrayElement(cursor);
-        }
-        if(isSynced[position]) {
-            if(isSelected[position]) {
+
+        if(initialState[SYNCED_INDEX]) {
+            if(initialState[SELECTED_INDEX]) {
                 status = syncedVisible;
                 state = 0;
             } else {
@@ -361,17 +345,26 @@ public class SelectCalendarsAdapter extends CursorTreeAdapter {
         }
 
         view.findViewById(R.id.color)
-            .setBackgroundDrawable(getColorChip(cursor.getInt(COLOR_COLUMN)));
-        setText(view, R.id.calendar, cursor.getString(NAME_COLUMN));
+            .setBackgroundDrawable(Utils.getColorChip(cursor.getInt(COLOR_COLUMN)));
+        String name = cursor.getString(NAME_COLUMN);
+        String owner = cursor.getString(OWNER_COLUMN);
+        if (mIsDuplicateName.containsKey(name) && mIsDuplicateName.get(name) &&
+                !name.equalsIgnoreCase(owner)) {
+            name = new StringBuilder(name)
+                    .append(Utils.OPEN_EMAIL_MARKER)
+                    .append(owner)
+                    .append(Utils.CLOSE_EMAIL_MARKER)
+                    .toString();
+        }
+        setText(view, R.id.calendar, name);
         setText(view, R.id.status, status);
         MultiStateButton button = (MultiStateButton) view.findViewById(R.id.multiStateButton);
-        long id = cursor.getLong(ID_COLUMN);
 
         //Set up the listeners so a click on the button will change the state.
         //The view already uses the onChildClick method in the activity.
-        ButtonListener bListener = new ButtonListener(id, position, account, view);
-        button.setOnClickListener(null);
-        button.setOnClickListener(bListener);
+        button.setTag(view);
+        view.setTag(id);
+        button.setOnClickListener(this);
         button.setButtonResources(SYNC_VIS_BUTTON_RES);
         button.setState(state);
     }
@@ -384,11 +377,6 @@ public class SelectCalendarsAdapter extends CursorTreeAdapter {
         String accountType = cursor.getString(accountTypeColumn);
         setText(view, R.id.account, account);
         setText(view, R.id.account_type, getLabelForType(accountType).toString());
-        //This occurs if the user adds a new account while Calendar is running in the background and
-        //after the user has entered the SelectCalendars screen at least once this session.
-        if(cursor.getCount() != mChildren.length) {
-            updateChildren(cursor);
-        }
     }
 
     @Override
@@ -396,8 +384,10 @@ public class SelectCalendarsAdapter extends CursorTreeAdapter {
         int accountColumn = groupCursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT);
         String account = groupCursor.getString(accountColumn);
         //Get all the calendars for just this account.
-        //TODO Move managedQuery into a background thread in CP2
-        Cursor childCursor = mActivity.managedQuery(Calendars.CONTENT_URI, PROJECTION,
+        Cursor childCursor = mChildrenCursors.get(account);
+        mCalendarsUpdater.startQuery(groupCursor.getPosition(),
+                account,
+                Calendars.CONTENT_URI, PROJECTION,
                 Calendars._SYNC_ACCOUNT + "=\"" + account + "\"" /*Selection*/,
                 null /* selectionArgs */,
                 Calendars.DISPLAY_NAME);