2 * Copyright (C) 2011 Morphoss Ltd
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 package com
.morphoss
.acal
.activity
;
22 import java
.util
.TreeMap
;
24 import android
.accounts
.Account
;
25 import android
.accounts
.AccountAuthenticatorResponse
;
26 import android
.accounts
.AccountManager
;
27 import android
.content
.ContentResolver
;
28 import android
.content
.ContentValues
;
29 import android
.content
.Intent
;
30 import android
.database
.Cursor
;
31 import android
.database
.DatabaseUtils
;
32 import android
.os
.Bundle
;
33 import android
.os
.RemoteException
;
34 import android
.preference
.Preference
;
35 import android
.preference
.Preference
.OnPreferenceClickListener
;
36 import android
.preference
.PreferenceActivity
;
37 import android
.preference
.PreferenceCategory
;
38 import android
.preference
.PreferenceScreen
;
39 import android
.util
.Log
;
40 import android
.view
.ContextMenu
;
41 import android
.view
.ContextMenu
.ContextMenuInfo
;
42 import android
.view
.MenuItem
;
43 import android
.view
.View
;
44 import android
.view
.View
.OnCreateContextMenuListener
;
45 import android
.widget
.AdapterView
.AdapterContextMenuInfo
;
46 import android
.widget
.Toast
;
48 import com
.morphoss
.acal
.Constants
;
49 import com
.morphoss
.acal
.R
;
50 import com
.morphoss
.acal
.ServiceManager
;
51 import com
.morphoss
.acal
.ServiceManagerCallBack
;
52 import com
.morphoss
.acal
.providers
.DavCollections
;
53 import com
.morphoss
.acal
.providers
.Servers
;
54 import com
.morphoss
.acal
.service
.AcalAuthenticator
;
55 import com
.morphoss
.acal
.service
.ServiceRequest
;
58 * <h3>Collection Configuration List - A list of collections that can be configured</h3>
61 * This class generates and displays the list of collections available in the dav_collection table. Selecting
62 * a collection will start the CollectionConfig activity.
65 * @author Morphoss Ltd
68 public class CollectionConfigList
extends PreferenceActivity
69 implements OnPreferenceClickListener
, OnCreateContextMenuListener
{
71 public static final String TAG
= "aCal CollectionConfigList";
74 // Data from the Collection Table
75 int collectionListCount
= 0;
76 private int[] collectionListIds
;
77 private Map
<Integer
, ContentValues
> collectionData
;
79 private Map
<Integer
, ContentValues
> serverData
;
81 // Context Menu Options
82 public static final int CONTEXT_SYNC_NOW
= 1;
83 public static final int CONTEXT_DISABLE
= 2;
85 private static final int CONTEXT_FORCE_FULL_RESYNC
= 3;
88 public static final int UPDATE_COLLECTION_CONFIG
= 0;
89 private boolean updateRequested
= false;
90 private int updateId
= -1;
92 private Cursor mCursor
;
94 private ServiceManager serviceManager
= null;
96 private PreferenceScreen preferenceRoot
;
98 private int serverListCount
;
100 private int[] preferenceListIds
;
102 // Needed for AcalAuthenticator
103 public static final String ACTION_CHOOSE
= "com.morphoss.acal.activity.CollectionConfigList.ACTION_CHOOSE";
106 * Get the list of collections and create the list view.
108 * @see android.app.Activity#onCreate(android.os.Bundle)
109 * @author Morphoss Ltd
112 public void onCreate(Bundle savedInstanceState
) {
113 super.onCreate(savedInstanceState
);
114 setContentView(R
.layout
.collections_list
);
116 // Get all of the collections from the database
117 getCollectionListItems();
119 // Create configuration screen
120 createPreferenceHierarchy();
121 setPreferenceScreen(this.preferenceRoot
);
122 this.preferenceRoot
.setOnPreferenceClickListener(this);
124 registerForContextMenu(this.getListView());
130 * This method connects to the database and gets all collection information. It creates a new ListAdapter and
131 * applies it to the ListView. It also updates our Fields and causes this activity to redraw itself.
132 * Should be called whenever there has been a change to the collection table
135 * @author Morphoss Ltd
137 private void getCollectionListItems() {
140 ContentResolver cr
= getContentResolver();
141 mCursor
= cr
.query(Servers
.CONTENT_URI
, null, Servers
.ACTIVE
, null, Servers
._ID
);
142 this.serverListCount
= mCursor
.getCount();
144 this.serverData
= new TreeMap
<Integer
,ContentValues
>();
145 mCursor
.moveToFirst();
146 while( ! mCursor
.isAfterLast() ) {
147 ContentValues cv
= new ContentValues();
148 DatabaseUtils
.cursorRowToContentValues(mCursor
, cv
);
149 int serverId
= cv
.getAsInteger(Servers
._ID
);
150 this.serverData
.put(serverId
, cv
);
151 mCursor
.moveToNext();
155 // Get Collections Data
156 mCursor
= cr
.query(DavCollections
.CONTENT_URI
, null, null, null, DavCollections
.SERVER_ID
+",lower("+DavCollections
.DISPLAYNAME
+")" );
157 collectionListCount
= mCursor
.getCount();
159 // Store data in useful structures
160 this.collectionListIds
= new int[collectionListCount
];
162 this.collectionData
= new TreeMap
<Integer
,ContentValues
>();
163 mCursor
.moveToFirst();
165 while( ! mCursor
.isAfterLast() ) {
166 ContentValues cv
= new ContentValues();
167 DatabaseUtils
.cursorRowToContentValues(mCursor
, cv
);
168 int collectionId
= cv
.getAsInteger(DavCollections
._ID
);
169 this.collectionListIds
[i
++] = collectionId
;
170 this.collectionData
.put(collectionId
, cv
);
171 mCursor
.moveToNext();
177 * <p>This method constructs all of the preference elements required</p>
178 * @return The preference screen that was created.
180 private void createPreferenceHierarchy() {
183 this.preferenceRoot
= getPreferenceManager().createPreferenceScreen(this);
184 this.preferenceRoot
.setPersistent(false); //We are not using the SharedPrefs system, we will persist data manually.
186 this.preferenceListIds
= new int[collectionListCount
+serverListCount
];
187 PreferenceCategory currentCategory
= null;
188 int lastServerId
= -1;
190 for (int i
= 0; i
< this.collectionListCount
; i
++) {
191 int collectionId
= collectionListIds
[i
];
192 ContentValues cv
= collectionData
.get(collectionId
);
193 int serverId
= cv
.getAsInteger(DavCollections
.SERVER_ID
);
194 if (serverData
.get(serverId
) == null || 1 != serverData
.get(serverId
).getAsInteger(Servers
.ACTIVE
))
196 if (lastServerId
!= serverId
) {
197 currentCategory
= new PreferenceCategory(this);
198 currentCategory
.setTitle(serverData
.get(serverId
).getAsString(Servers
.FRIENDLY_NAME
));
199 preferenceRoot
.addPreference(currentCategory
);
200 preferenceListIds
[prefRowId
++] = 0;
201 lastServerId
= serverId
;
203 String collectionColour
= cv
.getAsString(DavCollections
.COLOUR
);
204 CollectionConfigListItemPreference thisPreference
= new CollectionConfigListItemPreference(this);
205 thisPreference
.setLayoutResource(R
.layout
.collections_list_item
);
206 thisPreference
.setTitle(cv
.getAsString(DavCollections
.DISPLAYNAME
));
207 thisPreference
.setSummary(cv
.getAsString(DavCollections
.COLLECTION_PATH
));
208 thisPreference
.setCollectionColour(collectionColour
);
209 thisPreference
.setPersistent(false);
210 thisPreference
.setKey(Integer
.toString(collectionId
));
211 thisPreference
.setOnPreferenceClickListener(this);
212 preferenceListIds
[prefRowId
++] = collectionId
;
213 currentCategory
.addPreference(thisPreference
);
220 * Called when a user selects 'Sync Now' from the context menu. Schedules an immediate sync
221 * for this collection.
225 * The position in CollectionNames of the name of the collection we are to synchronise
226 * @return true if operation was successful
228 * @author Morphoss Ltd
230 private boolean syncCollection( int collectionId
, boolean fullCollectionResync
) {
232 if ( serviceManager
== null ) serviceManager
= new ServiceManager(this);
233 if ( fullCollectionResync
) {
234 serviceManager
.getServiceRequest().fullCollectionResync(collectionId
);
237 serviceManager
.getServiceRequest().syncCollectionNow(collectionId
);
241 catch ( RemoteException re
) {
242 Log
.e(TAG
, "Unable to send synchronisation request to service: "+re
.getMessage());
243 Toast
.makeText(CollectionConfigList
.this, "Request failed: "+re
.getMessage(), Toast
.LENGTH_SHORT
).show();
248 protected void onActivityResult(int requestCode
, int resultCode
, Intent data
) {
249 if (requestCode
== UPDATE_COLLECTION_CONFIG
&& resultCode
== RESULT_OK
) {
250 if (data
.hasExtra("UpdateRequired")) {
251 int cId
= data
.getIntExtra("UpdateRequired", -1);
254 updateRequested
= true;
262 * Called when a user selects 'Disable Collection' from the context menu.
266 * The position in CollectionNames of the name of the collection we are to disable
267 * @return true if operation was successful
269 * @author Morphoss Ltd
271 private boolean disableCollection(int collectionId
) {
272 return DavCollections
.collectionEnabled(false, collectionId
, getContentResolver());
275 public void createAuthenticatedAccount(int collectionId
) {
276 ContentValues collectionValues
= collectionData
.get(collectionId
);
277 int serverId
= collectionValues
.getAsInteger(DavCollections
.SERVER_ID
);
278 ContentValues serverValues
= Servers
.getRow(serverId
, getContentResolver());
279 String collectionName
= collectionValues
.getAsString(DavCollections
.DISPLAYNAME
);
280 String serverName
= serverValues
.getAsString(Servers
.FRIENDLY_NAME
);
281 Account account
= new Account(serverName
+ " - " + collectionName
, getString(R
.string
.AcalAccountType
));
282 Bundle userData
= new Bundle();
283 userData
.putString(AcalAuthenticator
.SERVER_ID
, serverValues
.getAsString(Servers
._ID
));
284 userData
.putString(AcalAuthenticator
.COLLECTION_ID
, collectionValues
.getAsString(DavCollections
._ID
));
285 userData
.putString(AcalAuthenticator
.USERNAME
, serverValues
.getAsString(Servers
.USERNAME
));
286 AccountManager am
= AccountManager
.get(this);
287 boolean accountCreated
= false;
289 accountCreated
= am
.addAccountExplicitly(account
, "", userData
);
290 } catch( Exception e
) {
291 Log
.println(Constants
.LOGD
, TAG
, Log
.getStackTraceString(e
));
294 if ( accountCreated
) {
297 Intent creator
= getIntent();
298 Bundle extras
= creator
.getExtras();
299 if (accountCreated
&& extras
!= null) {
300 AccountAuthenticatorResponse response
= extras
.getParcelable(AccountManager
.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE
);
301 Bundle result
= new Bundle();
302 result
.putString(AccountManager
.KEY_ACCOUNT_NAME
, account
.name
);
303 result
.putString(AccountManager
.KEY_ACCOUNT_TYPE
, getString(R
.string
.AcalAccountType
));
304 response
.onResult(result
);
311 * Handles context menu clicks
314 * @see android.app.Activity#onContextItemSelected(android.view.MenuItem)
315 * @author Morphoss Ltd
318 public boolean onContextItemSelected(MenuItem item
) {
320 AdapterContextMenuInfo info
= (AdapterContextMenuInfo
) item
.getMenuInfo();
321 int id
= preferenceListIds
[info
.position
];
322 if (Constants
.LOG_DEBUG
) Log
.println(Constants
.LOGD
, TAG
, "Context menu on preferenceItem " + info
.position
+ " which I reckon is id " + id
);
323 switch (item
.getItemId()) {
324 case CONTEXT_SYNC_NOW
:
325 return syncCollection(id
, false);
326 case CONTEXT_DISABLE
:
327 return disableCollection(id
);
328 case CONTEXT_FORCE_FULL_RESYNC
:
329 return syncCollection(id
, true);
334 catch (ClassCastException e
) {
341 * <h3>Click listener for Collection Configuration List.</h3>
344 * Called when a collection is selected from the list.
346 * Gets the appropriate String from this.collectionNames and uses it as a key to get the data from
347 * this.collectionData. Will start the Collection Configuration activity with this data. if 'Add Collection' was
348 * selected, Collection Configuration is sent a blank data set with MODEKEY=MODE_CREATE
351 * @see android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget.AdapterView,
352 * android.view.View, int, long)
353 * @author Morphoss Ltd
355 public boolean onPreferenceClick(Preference id
) {
356 int collectionId
= Integer
.parseInt(id
.getKey());
357 Intent i
= this.getIntent();
358 if ( android
.os
.Build
.VERSION
.SDK_INT
>= 8 && i
!= null && ACTION_CHOOSE
.equals(i
.getAction()) ) {
359 createAuthenticatedAccount(collectionId
);
362 // Create Intent to start new Activity
363 Intent collectionConfigIntent
= new Intent();
366 // Get the collection data for the selected collection
367 ContentValues toPass
= collectionData
.get(collectionId
);
369 // Begin new activity
370 collectionConfigIntent
.setClassName("com.morphoss.acal", "com.morphoss.acal.activity.CollectionConfiguration");
371 collectionConfigIntent
.putExtra("CollectionData", toPass
);
372 CollectionConfigList
.this.startActivityForResult(collectionConfigIntent
, UPDATE_COLLECTION_CONFIG
);
378 * Creates the context menus for each item in the list.
380 * @author Morphoss Ltd
383 public void onCreateContextMenu(ContextMenu menu
, View view
, ContextMenuInfo info
) {
384 menu
.setHeaderTitle(getString(R
.string
.Collection_Options
));
385 menu
.add(0, CollectionConfigList
.CONTEXT_SYNC_NOW
, 0, getString(R
.string
.Sync_collection_now
));
386 menu
.add(0, CollectionConfigList
.CONTEXT_DISABLE
, 0, getString(R
.string
.Disable_collection
));
387 menu
.add(0, CollectionConfigList
.CONTEXT_FORCE_FULL_RESYNC
, 0, getString(R
.string
.Force_full_resync
));
391 * Whenever this activity is resumed, update the collection list as it may have changed.
393 protected void onResume() {
396 //we must have a connection to continue
397 if (updateRequested
) {
398 updateRequested
= false;
401 if (serviceManager
!= null) serviceManager
.close();
403 catch ( Exception e
) {};
404 this.serviceManager
= new ServiceManager(this, new ServiceManagerCallBack() {
407 public void serviceConnected(ServiceRequest serviceRequest
) {
408 // TODO Auto-generated method stub
410 serviceRequest
.fullCollectionResync(updateId
);
411 } catch (RemoteException e
) {
412 // TODO Auto-generated catch block
413 Log
.w(TAG
,Log
.getStackTraceString(e
));
422 if (this.serviceManager
== null) serviceManager
= new ServiceManager(this);
426 public void onDestroy() {
428 if (this.serviceManager
!= null) this.serviceManager
.close();
429 if (mCursor
!= null && !mCursor
.isClosed()) mCursor
.close();
433 public void onPause() {
435 if (this.serviceManager
!= null) this.serviceManager
.close();
436 serviceManager
= null;