Accounts und Kontakte in Android Die letzten Wochen habe ich daran gearbeitet, die Kontakte der AddressTable von Leonardo und mir ins Android Adressbuch zu bringen. Dazu habe ich die AddressManagement-Applikation geschrieben. Theoretisch (Account für Kontakte) Um in Android Kontakte mit einem Server synchronisieren zu können, muss man zuerst einen Account erstellen. Ein Account wird meistens durch einen Benutzernamen und ein Passwort authentifiziert. Er beinhaltet die Daten, welche einem Benutzer von einem Server zur Verfügung stehen. Je nach Account können diverse Dinge, wie z.b. Mail, Fotos, Kontakte, Kalender, usw., synchronisiert werden. In diesem Wochenbericht schreibe ich nur über die Synchronisation der Kontakte eines Accounts. Die Accounts und Account-Daten sind in verschiedene Tabellen eingeteilt. In der ersten Tabelle stehen die Accounts selber. In einer weiteren Tabelle stehen die verschiedenen Personen(in Bild 1 'Contact'), aber noch keine Informationen zu ihnen. Eine weitere Untertabelle beinhaltet die Informationen welche Daten (in Bild 1 'raw contact') einer Person von welchem Account kommen. In der letzten Tabelle stehen dann schliesslich die Informationen (in Bild 1 'Data') selbst, wie der Name, Vorname, Telefonnummer, usw. Hierzu ein kleines Beispiel: Nehmen wir an, eine Benutzerin namens 'Emily Dickinson' hat 3 Accounts auf ihrem Mobiltelefon eingerichtet: emily.dickinson@gmail.com emilyd@gmail.com Twitter Account 'belle_of_amherst' Nun kennt sie einen Freund namens 'Thomas Higginson'. Sie öffnet einen Browser, fügt Thomas auf ihrem ersten Gmail Account hinzu als 'Thomas Higginson'. Später logt sie sich als emilyd@gmail.com ein und sendet
Thomas eine E-Mail, was ihn automatisch zum Adressbuch hinzufügt. Ebenfalls folgt sie 'colonel_tom' auf Twitter, welches der Account von Thomas ist. Emily möchte Thomas auf ihrem Mobiltelefon nun nicht 3-mal als einzelnen Kontakt haben, sondern einen Kontakt von Thomas der alle 3 Informationen zusammenträgt. Dank der obigen Struktur gibt es nun einen 'Contact', der alle Informationen enthält. Der Name ist mit dem Kontakt und dem 'emily.dickinson@gmail.com' Account verknüpft, die E-Mail ist mit dem Kontakt und dem 'emilyd@gmail.com' Account verknüpft. Der Twitteraccount von Thomas ist ebenfalls mit dem Kontakt und dem Twitteraccount von Emily verknüpft. Praktisch (Account hinzufügen in Android) Um einen neuen Account auf einem Android-Gerät erstellen zu können muss man zuerst seinen eigenen Account-Typ erstellen. In Android sind diverse Account-Typen schon vorinstalliert, wie zum Beispiel Google (Mail, Kalender, Kontakte, ) oder Facebook (Kontakte, Kalender, ). Das Erstellen eines Account-Typs ist eine Änderung der Systemeinstellungen. Dass die Applikation überhaupt in die Systemeinstellungen schreiben darf, muss dies im AndroidManifes.xml angegeben werden: <manifest> <!-- erlaubt Internetverbindung zu nutzen --> <uses-permission android:name="android.permission.internet"/> <!-- erlaubt auf die bestehenden Accounts zuzugreifen --> <uses-permission android:name="android.permission.get_accounts" /> <!-- erlaubt --> <uses-permission android:name="android.permission.use_credentials" /> <!-- erlaubt die Accounts zu managen --> <uses-permission android:name="android.permission.manage_accounts" /> <!-- erlaubt die Accounts zu authentifizieren --> <uses-permission android:name="android.permission.authenticate_accounts" /> <!-- erlaubt in die Systemeinstellungen zu schreiben --> <uses-permission android:name="android.permission.write_sync_settings"/> <!-- erlaubt von den Systemeinstellungen zu lesen --> <uses-permission android:name="android.permission.read_sync_settings"/>... </manifest> Ein Account-Typ ist in Android ein Service. Somit muss ein Service erstellt werden damit man seinen Account-Typ erstellen kann. Ein Service muss ebenfalls im AndroidMainfest.xml festgelegt sein: <manifest> <application> <service android:name="com.example.android.authenticationservice" android:exported="true"> <intent-filter> <action android:name="android.accounts.accountauthenticator" /> </intent-filter> <meta-data android:name="android.accounts.accountauthenticator"
android:resource="@xml/authenticator" /> </service> </application> </manifest> In diesem Fall muss es noch eine von mir erzeugte Klasse namens 'AuthenticationService' im Package 'com.example.android' geben, da dies im Punkt 'android:name' so festgelegt ist. Diese Klasse muss die Klasse 'Service' aus dem Package 'android.app' erweitern, damit sie als Service registriert werden kann. Bevor die Klasse 'AuthenticationService' geschrieben werden kann, muss eine andere Klasse erstellt werden, von welcher man dann eine Instanz erstellen kann. Die zu erstellende Klasse heisst bei mir 'Authenticator'. Sie erweitert die Klasse 'AbstractAccountAuthenticator'. Darin muss lediglich die Metode 'addaccount() überschrieben werden: public class Authenticator extends AbstractAccountAuthenticator { private final Context mcontext; public Authenticator(Context context) { super(context); mcontext = context; @Override public Bundle addaccount(accountauthenticatorresponse response, String accounttype, String authtokentype, String[] requiredfeatures, Bundle options) throws NetworkErrorException { final Intent intent = new Intent(mContext, AuthenticatorActivity.class); intent.putextra(accountmanager.key_account_authenticator_response, response); final Bundle bundle = new Bundle(); bundle.putparcelable(accountmanager.key_intent, intent); return bundle; Nun kann man in der 'AuthenticationService' Klasse eine Objekt-Variable vom Typ der eben erstellten 'Authenticator' Klasse erstellen: public class AuthenticationService extends Service { private Authenticator mauthenticator; @Override public void oncreate() { mauthenticator = new Authenticator(this); @Override public IBinder onbind(intent intent) { return mauthenticator.getibinder();
Um nun noch einen Account des erstellten Typs erstellen zu können, braucht es nochmals eine Activity. Sie erweitert die Activity 'AccountAuthenticatorActivity'. Ich habe sie 'AuthenticatorActivity' genannt. Diese Klasse sieht im Falle der AddressManagement-Applikation wie folgt aus:
1 public class AuthenticatorActivity extends AccountAuthenticatorActivity { 2 private AccountManager maccountmanager; 3 private UserLoginTask mauthtask = null; 4 private TextView mmessage; 5 private String murl = null; 6 private EditText murledit; 7 @Override 8 public void oncreate(bundle icicle) { 9 super.oncreate(icicle); 10 maccountmanager = AccountManager.get(this); 11 requestwindowfeature(window.feature_left_icon); 12 setcontentview(r.layout.login_activity); 13 getwindow().setfeaturedrawableresource( 14 Window.FEATURE_LEFT_ICON, android.r.drawable.ic_dialog_alert); 15 initviewelements(); 16 17 public void handlelogin(view view) { 18 readinputfromfields(); 19 if (isinputok()) { 20 mauthtask = new UserLoginTask(); 21 mauthtask.execute(); 22 else { 23 informuserabouterror(); 24 25 26 public class UserLoginTask extends AsyncTask<Void, Void, String> { 27 @Override 28 protected String doinbackground(void... params) {... 29 @Override 30 protected void onpostexecute(final String authtoken) { 31 mauthtask = null; 32 final Account account = new Account(mUrl, 33 "com.example.android.account"); 34 maccountmanager.addaccountexplicitly(account, null, null); 35 ContentResolver.setSyncAutomatically(account, 36 ContactsContract.AUTHORITY, true); 37 setinternresult(); 38 finish(); 39 40 @Override 41 protected void oncancelled() { 42 cancelled(); 43 44 private void setinternresult(){ 45 final Intent intent = new Intent(); 46 intent.putextra(accountmanager.key_account_name, murl); 47 intent.putextra(accountmanager.key_account_type, 48 Constants.ACCOUNT_TYPE); 49 setaccountauthenticatorresult(intent.getextras()); 50 setresult(result_ok, intent); 51 52 53
Als erstes werden diverse Variablen definiert, welche später gebraucht werden. Danach wird die Methode oncreate() der Activity 'AccountAuthenticatorActivity' überschrieben. Zu Beginn muss der AccountManager initialisiert werden Zeile 10. Auf der 11. Linie wird die Erlaubnis ein Fenster zu zeichnen gesetzt, welches anschliessend auf den folgenden Linien gezeichnet wird. Auf Linie 12 Wird das Gerüst des zu zeichnenden Fensters als xml-datei geladen. Diese sieht im Falle der AddressManagement-Applikation so aus wie auf der folgenden Seite. Auf den Linien 13+14 wird ein Bild für das Fenster geladen und auf der Linie 15 werden die Viewelemente geladen. In der xml-datei auf der nächsten Seite sieht man, dass beim 'Button' die Methode handlelogin() aufgerufen werden soll. Diese finden wir wieder auf der Linie 17. Sie holt den URL aus dem Textfeld. Anschliessend wird überprüft, ob der URL gültig ist oder nicht. Wenn er gültig ist wird die Variable des Typen 'UserLoginTask' initialisiert und ausgeführt. In diesem UserLoginTask wird der Vorgang, einen neuen Account zu erstellen, abteschlossen. Ansonsten wird der Benutzer nochmals gebeten einen gültigen Link einzugeben. Die Klasse 'UserLoginTask' ist in den Linien 26-44 definiert. Die Methode doinbackground() ist für den Account im Moment nicht weiter wichtig. Dafür muss die Methode onpostexecute() umgeschrieben werden. Es wird ein neuer Account erstellt auf den Linien 32+33, das erste Argument des Konstruktors der Klasse Account() ist der Name und das zweite ist der Typ. Im Falle der AddressManagement-Applikation habe ich den URL als Namen und den Package-Namen als Typ gewählt. Dies ist derselbe Typ, den wir im Manifest eingetragen haben. Dem AccountManager, auf der Zeile 34, wird der Account hinzugefügt. Auf Zeile 35+36 wird automatische Kontaktsynchronisation gesetzt. Auf Zeile 45 wird ein Intent verwendet. Ein Intent bietet eine Anlage zur Durchführung von spät Laufzeitbindungen zwischen dem Code in verschiedenen Applikationen. Im Grunde ist es eine Passive Datenstruktur, die eine abstrakte Beschreibung einer laufenden Aktion hält. Dem Intent werden ebenfalls als Namen der URL und als Typ den Package-Namen zugeteilt. Auf der Linie 49 wird das Resultat des Accounts gesetzt. Wenn diese Funktion nie aufgerufen wird oder 'null' das Resultat ist, wird die Anfrage einen neuen Account zu erstellen gestrichen. Auf der nächsten Linie wird das Gesamtresultat gesetzt. Die Letzte Methode finish(), auf der Zeile 38, schliesst das ganze ab. Die Methode oncancelled() braucht nur die Variable 'mauthtask' auf 'null' zu setzen.
<LinearLayout> <ScrollView android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1"> <LinearLayout android:layout_width="fill_parent" android:layout_weight="1" android:orientation="vertical" android:paddingtop="5dip" android:paddingbottom="13dip" android:paddingleft="20dip" android:paddingright="20dip"> <TextView android:id="@+id/message" android:textappearance="?android:attr/textappearancesmall" android:layout_width="wrap_content" android:layout_marginbottom="5dip" /> <TextView android:textappearance="?android:attr/textappearancesmall" android:layout_width="wrap_content" android:text="@string/login_activity_url_label" /> <EditText android:id="@+id/url_edit" android:singleline="true" android:layout_width="fill_parent" android:minwidth="250dip" android:scrollhorizontally="true" android:capitalize="none" android:autotext="false" android:inputtype="textemailaddress" android:text="http://" /> <TextView android:id="@+id/message_bottom" android:textappearance="?android:attr/textappearancesmall" android:layout_width="wrap_content" android:layout_marginbottom="5dip" /> </LinearLayout> </ScrollView> <FrameLayout android:layout_width="fill_parent" android:background="#c6c3c6" android:minheight="54dip" android:paddingtop="4dip" android:paddingleft="2dip" android:paddingright="2dip"> <Button android:id="@+id/ok_button" android:layout_width="wrap_content" android:layout_gravity="center_horizontal" android:minwidth="100dip" android:text="@string/login_activity_ok_button" android:onclick="handlelogin" /> </FrameLayout> </LinearLayout>
In einer XML Datei muss man angeben wie der Account schliesslich aussehen soll: <?xml version="1.0" encoding="utf-8"?> <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" android:accounttype="com.example.account" android:icon="@drawable/icon" android:smallicon="@drawable/icon" android:label="account" /> Anschliessend muss man in einer weiteren xml-datei angeben dass die Kontaktsynchronisation erlaubt werden soll: <?xml version="1.0" encoding="utf-8"?> <ContactsSource xmlns:android="http://schemas.android.com/apk/res/android"> <ContactsDataKind android:mimetype="vnd.android.cursor.item/vnd.samplesyncadapter.profile" android:icon="@drawable/icon" android:summarycolumn="data2" android:detailcolumn="data3" android:detailsocialsummary="true" /> </ContactsSource> Zum Schluss muss im AndroidManifes.xml noch angegeben werden, dass es die Activity 'AuthenticatorActivity', die vorhin erstellt wurde, gibt: <manifest> <application>... <activity android:name=".authenticatoractivity" android:label="@string/ui_activity_title" android:theme="@android:style/theme.dialog" android:excludefromrecents="true" android:configchanges="orientation" > </activity> </application> </manifest> Nun sollte man in der Lage sein, seinen eigenen Account des erstellten Account-Typs zu erstellen.