diff --git a/example/src/main/AndroidManifest.xml b/example/src/main/AndroidManifest.xml
index 5f3b82e..988fff4 100644
--- a/example/src/main/AndroidManifest.xml
+++ b/example/src/main/AndroidManifest.xml
@@ -2,9 +2,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/src/main/java/net/cattaka/android/snippets/example/AccountEditActivity.java b/example/src/main/java/net/cattaka/android/snippets/example/AccountEditActivity.java
new file mode 100644
index 0000000..7e288ba
--- /dev/null
+++ b/example/src/main/java/net/cattaka/android/snippets/example/AccountEditActivity.java
@@ -0,0 +1,104 @@
+package net.cattaka.android.snippets.example;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.content.Intent;
+import android.databinding.DataBindingUtil;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+
+import net.cattaka.android.snippets.example.data.AccountContainer;
+import net.cattaka.android.snippets.example.databinding.ActivityAccountEditBinding;
+
+public class AccountEditActivity extends AppCompatActivity implements View.OnClickListener {
+ public static final String KEY_ORIG_ACCOUNT = "et.cattaka.android.snippets.example.AccountEditActivity.ORIG_ACCOUNT";
+
+ public static Intent createIntent(@NonNull Context context, @NonNull AccountContainer origAccountContainer) {
+ Intent intent = new Intent(context, AccountEditActivity.class);
+ intent.putExtra(KEY_ORIG_ACCOUNT, origAccountContainer);
+ return intent;
+ }
+
+ ActivityAccountEditBinding mBinding;
+ AccountContainer mOrigAccountContainer;
+
+ AccountManager mAccountManager;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mBinding = DataBindingUtil.setContentView(this, R.layout.activity_account_edit);
+
+ mOrigAccountContainer = getIntent().getParcelableExtra(KEY_ORIG_ACCOUNT);
+
+ mBinding.buttonCancel.setOnClickListener(this);
+ mBinding.buttonRemove.setOnClickListener(this);
+ mBinding.buttonOk.setOnClickListener(this);
+
+ if (mOrigAccountContainer != null) {
+ mBinding.editAccountName.setText(mOrigAccountContainer.getAccount().name);
+ mBinding.editAuthToken.setText(mOrigAccountContainer.getAuthToken());
+ mBinding.buttonRemove.setVisibility(View.VISIBLE);
+ } else {
+ mOrigAccountContainer = new AccountContainer();
+ mBinding.buttonRemove.setVisibility(View.INVISIBLE);
+ }
+
+ mAccountManager = AccountManager.get(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.button_cancel) {
+ finish();
+ } else if (v.getId() == R.id.button_remove) {
+ onClickRemove();
+ } else if (v.getId() == R.id.button_ok) {
+ onClickOk();
+ }
+ }
+
+ private void onClickOk() {
+ String accountType = getString(R.string.account_manager_account_type);
+ String name = String.valueOf(mBinding.editAccountName.getText());
+ String authToken = String.valueOf(mBinding.editAuthToken.getText());
+ if (mOrigAccountContainer.getAccount() != null) {
+ // TODO: Show block
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mAccountManager.renameAccount(mOrigAccountContainer.getAccount(), name, null, null);
+ mAccountManager.setAuthToken(mOrigAccountContainer.getAccount(), Constants.AUTH_TOKEN_TYPE, authToken);
+ finish();
+ } else {
+ // FIXME: Can not rename correctly with older devices
+ mAccountManager.removeAccount(mOrigAccountContainer.getAccount(), future -> {
+ Account account = new Account(name, accountType);
+ mAccountManager.addAccountExplicitly(account, null, null);
+ mAccountManager.setAuthToken(account, Constants.AUTH_TOKEN_TYPE, authToken);
+ finish();
+ }, null);
+ }
+ } else {
+ Account account = new Account(name, accountType);
+ mAccountManager.addAccountExplicitly(account, null, null);
+ mAccountManager.setAuthToken(account, Constants.AUTH_TOKEN_TYPE, authToken);
+ finish();
+ }
+ }
+
+ private void onClickRemove() {
+ if (mOrigAccountContainer.getAccount() != null) {
+ // TODO: Show block
+ mAccountManager.removeAccount(mOrigAccountContainer.getAccount(), future -> {
+ finish();
+ }, null);
+ } else {
+ // error case
+ finish();
+ }
+ }
+}
diff --git a/example/src/main/java/net/cattaka/android/snippets/example/AccountsListActivity.java b/example/src/main/java/net/cattaka/android/snippets/example/AccountsListActivity.java
new file mode 100644
index 0000000..483a7dd
--- /dev/null
+++ b/example/src/main/java/net/cattaka/android/snippets/example/AccountsListActivity.java
@@ -0,0 +1,131 @@
+package net.cattaka.android.snippets.example;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.Toast;
+
+import net.cattaka.android.adaptertoolbox.adapter.ScrambleAdapter;
+import net.cattaka.android.adaptertoolbox.adapter.listener.ListenerRelay;
+import net.cattaka.android.snippets.example.adapter.factory.AccountViewHolderFactory;
+import net.cattaka.android.snippets.example.data.AccountContainer;
+import net.cattaka.android.snippets.example.databinding.ActivityAccountsListBinding;
+import net.cattaka.android.snippets.example.tracker.IScreen;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Locale;
+
+public class AccountsListActivity extends AppCompatActivity implements
+ IScreen,
+ View.OnClickListener {
+ ListenerRelay, RecyclerView.ViewHolder> mListenerRelay = new ListenerRelay, RecyclerView.ViewHolder>() {
+ @Override
+ public void onClick(@NonNull RecyclerView recyclerView, @NonNull ScrambleAdapter> adapter, @NonNull RecyclerView.ViewHolder holder, @NonNull View view) {
+ super.onClick(recyclerView, adapter, holder, view);
+ if (holder instanceof AccountViewHolderFactory.ViewHolder) {
+ Account account = mAdapter.getItemAt(holder.getAdapterPosition());
+ if (view.getId() == R.id.button_info) {
+ onClickButtonInfo(account);
+ } else {
+ obtainAccountContainer(account, arg -> {
+ startActivity(AccountEditActivity.createIntent(me, arg));
+ });
+ }
+ }
+ }
+ };
+
+ AccountsListActivity me = this;
+ ActivityAccountsListBinding mBinding;
+ AccountManager mAccountManager;
+
+ ScrambleAdapter mAdapter;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mBinding = DataBindingUtil.setContentView(this, R.layout.activity_accounts_list);
+
+ mBinding.buttonAdd.setOnClickListener(this);
+
+ mAdapter = new ScrambleAdapter<>(this, new ArrayList<>(), mListenerRelay, new AccountViewHolderFactory());
+ mBinding.recycler.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
+ mBinding.recycler.setAdapter(mAdapter);
+
+ mAccountManager = AccountManager.get(this);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ reloadAccounts();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.button_add) {
+ startActivity(AccountEditActivity.createIntent(this, null));
+ }
+ }
+
+ private void reloadAccounts() {
+ String accountType = getString(R.string.account_manager_account_type);
+ Account[] accounts = mAccountManager.getAccountsByType(accountType);
+
+ mAdapter.getItems().clear();
+ mAdapter.getItems().addAll(Arrays.asList(accounts));
+ mAdapter.notifyDataSetChanged();
+ }
+
+ private void onClickButtonInfo(@NonNull Account account) {
+ obtainAccountContainer(account, arg -> {
+ String name = arg.getAccount().name;
+ String accountType = arg.getAccount().type;
+ String authToken = arg.getAuthToken();
+ String text = String.format(Locale.ROOT, "name = %s\naccountType = %s\nauthToken = %s\n", name, accountType, authToken);
+ Toast.makeText(me, text, Toast.LENGTH_SHORT).show();
+ }
+ );
+ }
+
+ private void obtainAccountContainer(@NonNull Account account, @NonNull Consumer consumer) {
+ mAccountManager.getAuthToken(
+ account,
+ Constants.AUTH_TOKEN_TYPE,
+ true,
+ future -> {
+ if (future.isDone()) {
+ try {
+ Bundle bundle = future.getResult();
+ String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
+
+ AccountContainer container = new AccountContainer();
+ container.setAccount(account);
+ container.setAuthToken(authToken);
+ consumer.accept(container);
+ } catch (IOException e) {
+ // ignore
+ } catch (OperationCanceledException e) {
+ // ignore
+ } catch (AuthenticatorException e) {
+ // ignore
+ }
+ }
+ }, null);
+ }
+
+ private interface Consumer {
+ void accept(T arg);
+ }
+}
diff --git a/example/src/main/java/net/cattaka/android/snippets/example/Constants.java b/example/src/main/java/net/cattaka/android/snippets/example/Constants.java
index 4514a0a..01f0346 100644
--- a/example/src/main/java/net/cattaka/android/snippets/example/Constants.java
+++ b/example/src/main/java/net/cattaka/android/snippets/example/Constants.java
@@ -7,4 +7,6 @@ public class Constants {
public static final String DB_NAME = "database";
public static final int DB_VERSION = 1;
public static final String PREF_NAME = "pref";
+
+ public static final String AUTH_TOKEN_TYPE = "my_auth_token_type";
}
diff --git a/example/src/main/java/net/cattaka/android/snippets/example/MainActivity.java b/example/src/main/java/net/cattaka/android/snippets/example/MainActivity.java
index 8ad1d94..202b8f4 100644
--- a/example/src/main/java/net/cattaka/android/snippets/example/MainActivity.java
+++ b/example/src/main/java/net/cattaka/android/snippets/example/MainActivity.java
@@ -31,6 +31,9 @@ public class MainActivity extends AppCompatActivity implements IScreen {
new ActivityEntry("Progress", MotionLayoutProgressActivity.class, Build.VERSION_CODES.JELLY_BEAN_MR2),
new ActivityEntry("Morph", MotionLayoutMorphActivity.class, Build.VERSION_CODES.JELLY_BEAN_MR2)
),
+ new ActivityEntry("AccountManager", null,
+ new ActivityEntry("Accounts List", AccountsListActivity.class)
+ ),
new ActivityEntry("With Google Applications", null,
new ActivityEntry("Pick from Google Photos", PickFromGooglePhotosActivity.class)
),
diff --git a/example/src/main/java/net/cattaka/android/snippets/example/account_manager/AccountAuthenticator.java b/example/src/main/java/net/cattaka/android/snippets/example/account_manager/AccountAuthenticator.java
new file mode 100644
index 0000000..4ba7415
--- /dev/null
+++ b/example/src/main/java/net/cattaka/android/snippets/example/account_manager/AccountAuthenticator.java
@@ -0,0 +1,70 @@
+package net.cattaka.android.snippets.example.account_manager;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.NetworkErrorException;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import net.cattaka.android.snippets.example.AccountEditActivity;
+import net.cattaka.android.snippets.example.Constants;
+
+public class AccountAuthenticator extends AbstractAccountAuthenticator {
+ Context mContext;
+
+ public AccountAuthenticator(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+ return null;
+ }
+
+ @Override
+ public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
+ Intent intent = new Intent(mContext, AccountEditActivity.class);
+ intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AccountManager.KEY_INTENT, intent);
+ return bundle;
+ }
+
+ @Override
+ public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
+ return null;
+ }
+
+ @Override
+ public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
+ AccountManager manager = AccountManager.get(mContext);
+ String authToken = manager.peekAuthToken(account, Constants.AUTH_TOKEN_TYPE);
+
+ Bundle result = new Bundle();
+ result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+ if (Constants.AUTH_TOKEN_TYPE.equals(authTokenType)) {
+ result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
+ }
+ return result;
+ }
+
+ @Override
+ public String getAuthTokenLabel(String authTokenType) {
+ return null;
+ }
+
+ @Override
+ public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
+ return null;
+ }
+
+ @Override
+ public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
+ return null;
+ }
+}
diff --git a/example/src/main/java/net/cattaka/android/snippets/example/account_manager/AuthenticatorService.java b/example/src/main/java/net/cattaka/android/snippets/example/account_manager/AuthenticatorService.java
new file mode 100644
index 0000000..0c58512
--- /dev/null
+++ b/example/src/main/java/net/cattaka/android/snippets/example/account_manager/AuthenticatorService.java
@@ -0,0 +1,23 @@
+package net.cattaka.android.snippets.example.account_manager;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class AuthenticatorService extends Service {
+ private AccountAuthenticator mAccountAuthenticator;
+
+ public AuthenticatorService() {
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mAccountAuthenticator = new AccountAuthenticator(this);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return this.mAccountAuthenticator.getIBinder();
+ }
+}
diff --git a/example/src/main/java/net/cattaka/android/snippets/example/adapter/factory/AccountViewHolderFactory.java b/example/src/main/java/net/cattaka/android/snippets/example/adapter/factory/AccountViewHolderFactory.java
new file mode 100644
index 0000000..72af5cd
--- /dev/null
+++ b/example/src/main/java/net/cattaka/android/snippets/example/adapter/factory/AccountViewHolderFactory.java
@@ -0,0 +1,49 @@
+package net.cattaka.android.snippets.example.adapter.factory;
+
+import android.accounts.Account;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import net.cattaka.android.adaptertoolbox.adapter.ScrambleAdapter;
+import net.cattaka.android.adaptertoolbox.adapter.listener.ForwardingListener;
+import net.cattaka.android.snippets.example.R;
+
+public class AccountViewHolderFactory extends ScrambleAdapter.AbsViewHolderFactory {
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ScrambleAdapter> adapter, @NonNull ViewGroup parent, @NonNull ForwardingListener, RecyclerView.ViewHolder> forwardingListener) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_account, parent, false);
+ ViewHolder vh = new ViewHolder(view);
+ view.setOnClickListener(forwardingListener);
+ vh.buttonEdit.setOnClickListener(forwardingListener);
+ vh.buttonInfo.setOnClickListener(forwardingListener);
+ return vh;
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ScrambleAdapter adapter, @NonNull ViewHolder holder, int position, Object object) {
+ Account item = (Account) object;
+ holder.textLabel.setText(item != null ? item.name : null);
+ }
+
+ @Override
+ public boolean isAssignable(Object object) {
+ return object instanceof Account;
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ TextView textLabel;
+ View buttonEdit;
+ View buttonInfo;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+ textLabel = itemView.findViewById(R.id.text_label);
+ buttonEdit = itemView.findViewById(R.id.button_edit);
+ buttonInfo = itemView.findViewById(R.id.button_info);
+ }
+ }
+}
diff --git a/example/src/main/java/net/cattaka/android/snippets/example/data/AccountContainer.java b/example/src/main/java/net/cattaka/android/snippets/example/data/AccountContainer.java
new file mode 100644
index 0000000..b80ba49
--- /dev/null
+++ b/example/src/main/java/net/cattaka/android/snippets/example/data/AccountContainer.java
@@ -0,0 +1,52 @@
+package net.cattaka.android.snippets.example.data;
+
+import android.accounts.Account;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class AccountContainer implements Parcelable {
+ public static Creator CREATOR = new Creator() {
+ @Override
+ public AccountContainer createFromParcel(Parcel source) {
+ AccountContainer container = new AccountContainer();
+ container.setAccount(source.readParcelable(AccountContainer.class.getClassLoader()));
+ container.setAuthToken(source.readString());
+ return container;
+ }
+
+ @Override
+ public AccountContainer[] newArray(int size) {
+ return new AccountContainer[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(account, flags);
+ dest.writeString(authToken);
+ }
+
+ private Account account;
+ private String authToken;
+
+ public Account getAccount() {
+ return account;
+ }
+
+ public void setAccount(Account account) {
+ this.account = account;
+ }
+
+ public String getAuthToken() {
+ return authToken;
+ }
+
+ public void setAuthToken(String authToken) {
+ this.authToken = authToken;
+ }
+}
diff --git a/example/src/main/res/drawable/ic_add_white_24dp.xml b/example/src/main/res/drawable/ic_add_white_24dp.xml
new file mode 100644
index 0000000..e3979cd
--- /dev/null
+++ b/example/src/main/res/drawable/ic_add_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/example/src/main/res/drawable/ic_edit_white_24dp.xml b/example/src/main/res/drawable/ic_edit_white_24dp.xml
new file mode 100644
index 0000000..46462b5
--- /dev/null
+++ b/example/src/main/res/drawable/ic_edit_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/example/src/main/res/drawable/ic_info_outline_white_24dp.xml b/example/src/main/res/drawable/ic_info_outline_white_24dp.xml
new file mode 100644
index 0000000..af0d4d0
--- /dev/null
+++ b/example/src/main/res/drawable/ic_info_outline_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/example/src/main/res/layout/activity_account_edit.xml b/example/src/main/res/layout/activity_account_edit.xml
new file mode 100644
index 0000000..64c8567
--- /dev/null
+++ b/example/src/main/res/layout/activity_account_edit.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/src/main/res/layout/activity_accounts_list.xml b/example/src/main/res/layout/activity_accounts_list.xml
new file mode 100644
index 0000000..baf6f40
--- /dev/null
+++ b/example/src/main/res/layout/activity_accounts_list.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/example/src/main/res/layout/item_account.xml b/example/src/main/res/layout/item_account.xml
new file mode 100644
index 0000000..d6d2104
--- /dev/null
+++ b/example/src/main/res/layout/item_account.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml
index 3545526..2c46f0f 100644
--- a/example/src/main/res/values/strings.xml
+++ b/example/src/main/res/values/strings.xml
@@ -1,9 +1,14 @@
Snippets Example
+ net.cattaka.android.snippets
First
Second
Third
Fourth
cover image
+ Account
+ Account name
+ Auth token
+ Remove
diff --git a/example/src/main/res/xml/authenticator.xml b/example/src/main/res/xml/authenticator.xml
new file mode 100644
index 0000000..6b1c583
--- /dev/null
+++ b/example/src/main/res/xml/authenticator.xml
@@ -0,0 +1,5 @@
+
+