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 a3eab03..3762362 100644 (file)
 
 package com.android.calendar;
 
+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.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;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.CursorAdapter;
+import android.widget.CursorTreeAdapter;
 import android.widget.TextView;
 
-public class SelectCalendarsAdapter extends CursorAdapter {
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 
-    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;
-
-    /* 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";
 
+    // 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,
+        R.drawable.widget_off
+    };
+
     private final LayoutInflater mInflater;
     private final ContentResolver mResolver;
-    private final ContentValues mValues = new ContentValues();
-    private Boolean mIsChecked[] = null;
-    private static final Boolean CHECKED = true;
-    private static final Boolean UNCHECKED = false;
-
-    private class CheckBoxListener implements CheckBox.OnCheckedChangeListener {
-        private final long mCalendarId;
-        private final int mPosition;
-
-        private CheckBoxListener(long calendarId, int position) {
-            mPosition = position;
-            mCalendarId = calendarId;
-        }
-        
-        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-            Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, mCalendarId);
-            mValues.clear();
-            int checked = isChecked ? 1 : 0;
-            mValues.put(Calendars.SELECTED, checked);
-            mResolver.update(uri, mValues, null, null);
-            mIsChecked[mPosition] = isChecked ? CHECKED : UNCHECKED;
+    private final SelectCalendarsActivity mActivity;
+    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 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;
+
+    private class AsyncCalendarsUpdater extends AsyncQueryHandler {
+
+        public AsyncCalendarsUpdater(ContentResolver cr) {
+            super(cr);
+        }
+
+        @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 {
+                    mActivity.stopManagingCursor(currentCursor);
+                    currentCursor.close();
+                    mChildrenCursors.remove(cookie);
+                }
+            }
+            // 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();
+        }
+
+        /**
+         * 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;
+            }
+
+            int numColumns = c1.getColumnCount();
+            if (numColumns != c2.getColumnCount()) {
+                return false;
+            }
+
+            if (c1.getCount() != c2.getCount()) {
+                return false;
+            }
+
+            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;
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        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;
         }
     }
 
-    private void updateIsCheckedArray(int cursorCount) {
-        mIsChecked = new Boolean[cursorCount];
+    /**
+     * 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);
+        }
+
+        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);
+        }
     }
 
-    public SelectCalendarsAdapter(Context context, Cursor cursor) {
-        super(context, cursor);
+    public SelectCalendarsAdapter(Context context, Cursor cursor, SelectCalendarsActivity act) {
+        super(cursor, context);
+        syncedVisible = context.getString(R.string.synced_visible);
+        syncedNotVisible = context.getString(R.string.synced_not_visible);
+        notSyncedNotVisible = context.getString(R.string.not_synced_not_visible);
+
         mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         mResolver = context.getContentResolver();
-        updateIsCheckedArray(cursor.getCount());
+        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!");
+        }
+        //Collect proper description for account types
+        mAuthDescs = AccountManager.get(context).getAuthenticatorTypes();
+        for (int i = 0; i < mAuthDescs.length; i++) {
+            mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]);
+        }
     }
 
-    @Override
-    public View newView(Context context, Cursor cursor, ViewGroup parent) {
-        return mInflater.inflate(R.layout.calendar_item, parent, false);
-    }
+    /*
+     * 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() {
+        // 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;
 
-    @Override
-    public void bindView(View view, Context context, Cursor cursor) {
-        int idColumn = cursor.getColumnIndexOrThrow(Calendars._ID);
-        int nameColumn = cursor.getColumnIndexOrThrow(Calendars.DISPLAY_NAME);
-        int selectedColumn = cursor.getColumnIndexOrThrow(Calendars.SELECTED);
-        int colorColumn = cursor.getColumnIndexOrThrow(Calendars.COLOR);
-        view.findViewById(R.id.color).setBackgroundDrawable(getColorChip(cursor.getInt(colorColumn)));
-        setText(view, R.id.calendar, cursor.getString(nameColumn));
-        CheckBox box = (CheckBox) view.findViewById(R.id.checkbox);
-        long id = cursor.getLong(idColumn);
-
-        // Update mIsChecked array is needed
-        int cursorCount = cursor.getCount();
-        if (cursorCount != mIsChecked.length) {
-            updateIsCheckedArray(cursorCount);
-        }
-
-        // If the value hasn't changed, read from cursor; otherwise, read from mIsChecked array.
-        boolean checked;
-        int position = cursor.getPosition();
-        if (mIsChecked[position] == null) {
-            checked = cursor.getInt(selectedColumn) != 0;
-        } else {
-            checked = (mIsChecked[position] == CHECKED);
-        }
+        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;
 
-        box.setOnCheckedChangeListener(null);
-        box.setChecked(checked);
-        box.setOnCheckedChangeListener(new CheckBoxListener(id, position));
+            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);
+        }
     }
 
     private static void setText(View view, int id, String text) {
@@ -126,26 +292,117 @@ public class SelectCalendarsAdapter extends CursorAdapter {
         TextView textView = (TextView) view.findViewById(id);
         textView.setText(text);
     }
-    
-    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;
+
+    /**
+     * Gets the label associated with a particular account type. If none found, return null.
+     * @param accountType the type of account
+     * @return a CharSequence for the label or null if one cannot be found.
+     */
+    protected CharSequence getLabelForType(final String accountType) {
+        CharSequence label = null;
+        if (mTypeToAuthDescription.containsKey(accountType)) {
+             try {
+                 AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
+                 Context authContext = mActivity.createPackageContext(desc.packageName, 0);
+                 label = authContext.getResources().getText(desc.labelId);
+             } catch (PackageManager.NameNotFoundException e) {
+                 Log.w(TAG, "No label for account type " + ", type " + accountType);
+             }
+        }
+        return label;
+    }
+
+    @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);
+
+        // 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);
+        }
+        // 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);
+        }
+
+        if(initialState[SYNCED_INDEX]) {
+            if(initialState[SELECTED_INDEX]) {
+                status = syncedVisible;
+                state = 0;
+            } else {
+                status = syncedNotVisible;
+                state = 1;
+            }
+        }
+
+        view.findViewById(R.id.color)
+            .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);
+
+        //Set up the listeners so a click on the button will change the state.
+        //The view already uses the onChildClick method in the activity.
+        button.setTag(view);
+        view.setTag(id);
+        button.setOnClickListener(this);
+        button.setButtonResources(SYNC_VIS_BUTTON_RES);
+        button.setState(state);
+    }
+
+    @Override
+    protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
+        int accountColumn = cursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT);
+        int accountTypeColumn = cursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT_TYPE);
+        String account = cursor.getString(accountColumn);
+        String accountType = cursor.getString(accountTypeColumn);
+        setText(view, R.id.account, account);
+        setText(view, R.id.account_type, getLabelForType(accountType).toString());
+    }
+
+    @Override
+    protected Cursor getChildrenCursor(Cursor groupCursor) {
+        int accountColumn = groupCursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT);
+        String account = groupCursor.getString(accountColumn);
+        //Get all the calendars for just this account.
+        Cursor childCursor = mChildrenCursors.get(account);
+        mCalendarsUpdater.startQuery(groupCursor.getPosition(),
+                account,
+                Calendars.CONTENT_URI, PROJECTION,
+                Calendars._SYNC_ACCOUNT + "=\"" + account + "\"" /*Selection*/,
+                null /* selectionArgs */,
+                Calendars.DISPLAY_NAME);
+        return childCursor;
+    }
+
+    @Override
+    protected View newChildView(Context context, Cursor cursor, boolean isLastChild,
+            ViewGroup parent) {
+        return mInflater.inflate(R.layout.calendar_item, parent, false);
+    }
+
+    @Override
+    protected View newGroupView(Context context, Cursor cursor, boolean isExpanded,
+            ViewGroup parent) {
+        return mInflater.inflate(R.layout.account_item, parent, false);
     }
 }