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 @@ + + + + + + + + + + + + + + + + + +