Java tutorial
/******************************************************************************* * Copyright (c) 2012 MASConsult Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package eu.masconsult.bgbanking.activity.fragment; import static android.provider.BaseColumns._ID; import static eu.masconsult.bgbanking.provider.BankingContract.BankAccount.ACCOUNT_NAME; import static eu.masconsult.bgbanking.provider.BankingContract.BankAccount.ACCOUNT_TYPE; import static eu.masconsult.bgbanking.provider.BankingContract.BankAccount.COLUMN_NAME_AVAILABLE_BALANCE; import static eu.masconsult.bgbanking.provider.BankingContract.BankAccount.COLUMN_NAME_BALANCE; import static eu.masconsult.bgbanking.provider.BankingContract.BankAccount.COLUMN_NAME_CURRENCY; import static eu.masconsult.bgbanking.provider.BankingContract.BankAccount.COLUMN_NAME_IBAN; import static eu.masconsult.bgbanking.provider.BankingContract.BankAccount.COLUMN_NAME_LAST_TRANSACTION_DATE; import static eu.masconsult.bgbanking.provider.BankingContract.BankAccount.COLUMN_NAME_NAME; import static eu.masconsult.bgbanking.provider.BankingContract.BankAccount.CONTENT_URI; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.OnAccountsUpdateListener; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.ResourceCursorAdapter; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ListAdapter; import android.widget.TextView; import com.actionbarsherlock.app.SherlockListFragment; import com.commonsware.cwac.adapter.AdapterWrapper; import com.commonsware.cwac.merge.MergeAdapter; import eu.masconsult.bgbanking.BankingApplication; import eu.masconsult.bgbanking.R; import eu.masconsult.bgbanking.banks.Bank; import eu.masconsult.bgbanking.provider.BankingContract; import eu.masconsult.bgbanking.sync.SyncAdapter; import eu.masconsult.bgbanking.utils.Convert; import eu.masconsult.bgbanking.utils.SampleCursor; public class AccountsListFragment extends SherlockListFragment implements LoaderManager.LoaderCallbacks<Cursor>, OnAccountsUpdateListener { protected static final String TAG = BankingApplication.TAG + "AccountsListFragment"; // This is the Adapter being used to display the list's data. MergeAdapter mAdapter; // If non-null, this is the current filter the user has provided. String mCurFilter; private AccountManager accountManager; SparseArray<CursorAdapter> adapters = new SparseArray<CursorAdapter>(); SparseArray<Account> accounts = new SparseArray<Account>(); @Override public void onActivityCreated(Bundle savedInstanceState) { Log.v(TAG, "onActivityCreated"); super.onActivityCreated(savedInstanceState); // Give some text to display if there is no data. In a real // application this would come from a resource. setEmptyText("No bank accounts"); // We have a menu item to show in action bar. setHasOptionsMenu(true); // Start out with a progress indicator. setListShown(false); } private void populateList() { Log.v(TAG, "populateList"); // Create an empty adapter we will use to display the loaded data. mAdapter = new MergeAdapter(); accounts.clear(); adapters.clear(); // Prepare the loader. Either re-connect with an existing one, // or start a new one. Bank[] banks = Bank.values(); for (Bank bank : banks) { Account[] accounts = accountManager.getAccountsByType(bank.getAccountType(getActivity())); for (Account account : accounts) { addAccount(bank, account, false); } } if (accounts.size() == 0) { // show sample accounts for (Bank bank : banks) { addAccount(bank, new Account(getString(R.string.sample_account_name), bank.getAccountType(getActivity())), true); } } Log.v(TAG, String.format("found %d accounts", accounts.size())); setListAdapter(mAdapter); } private void addAccount(Bank bank, Account account, boolean sample) { View header = getActivity().getLayoutInflater().inflate(R.layout.row_bank_account_header, null); View bank_icon = header.findViewById(R.id.bank_icon); if (bank_icon != null && bank_icon instanceof ImageView) { ((ImageView) bank_icon).setImageResource(bank.iconResource); } View bank_name = header.findViewById(R.id.bank_name); if (bank_name != null && bank_name instanceof TextView) { ((TextView) bank_name).setText(bank.labelRes); } View account_name = header.findViewById(R.id.account_name); if (account_name != null && account_name instanceof TextView) { ((TextView) account_name).setText(account.name); } mAdapter.addView(header); BankAccountsAdapter adapter = new BankAccountsAdapter(getActivity(), R.layout.row_bank_account, null, 0); EmptyBankAccountsAdapter adapter2 = new EmptyBankAccountsAdapter(getActivity(), account, adapter); adapters.put(account.hashCode(), adapter); mAdapter.addAdapter(adapter2); accounts.put(account.hashCode(), account); if (sample) { Log.d(TAG, "Adding sample account cursor"); adapter.swapCursor(new SampleCursor(getActivity())); } else { Log.d(TAG, String.format("Requesting loader %d", account.hashCode())); getLoaderManager().initLoader(account.hashCode(), null, this); } } @Override public void onAttach(Activity activity) { Log.v(TAG, "onAttach"); super.onAttach(activity); accountManager = AccountManager.get(getActivity()); accountManager.addOnAccountsUpdatedListener(this, null, true); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(SyncAdapter.START_SYNC); intentFilter.addAction(SyncAdapter.STOP_SYNC); getActivity().registerReceiver(syncReceiver, intentFilter); populateList(); } @Override public void onDetach() { Log.v(TAG, "onDetach"); getActivity().unregisterReceiver(syncReceiver); accountManager.removeOnAccountsUpdatedListener(this); accountManager = null; super.onDetach(); } // These are the Contacts rows that we will retrieve. static final String[] ACCOUNTS_SUMMARY_PROJECTION = new String[] { _ID, COLUMN_NAME_IBAN, COLUMN_NAME_NAME, COLUMN_NAME_CURRENCY, COLUMN_NAME_BALANCE, COLUMN_NAME_AVAILABLE_BALANCE, COLUMN_NAME_LAST_TRANSACTION_DATE, }; @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri = CONTENT_URI; Account account = accounts.get(id); if (account == null) { return null; } Log.v(TAG, String.format("creating cursor loader for account: %s", account)); // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. return new CursorLoader(getActivity(), baseUri, ACCOUNTS_SUMMARY_PROJECTION, ACCOUNT_NAME + "=? AND " + ACCOUNT_TYPE + "=?", new String[] { account.name, account.type }, COLUMN_NAME_NAME + " COLLATE LOCALIZED ASC"); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { Log.v(TAG, String.format("onLoadFinished: %d with %d records", loader.getId(), data.getCount())); // Swap the new cursor in. (The framework will take care of closing // the old cursor once we return.) swapCursorFromLoader(loader, data); // The list should now be shown. if (isResumed()) { setListShown(true); } else { setListShownNoAnimation(true); } } private CursorAdapter getCursorAdapter(Loader<Cursor> loader) { return adapters.get(loader.getId()); } @Override public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. swapCursorFromLoader(loader, null); } private void swapCursorFromLoader(Loader<Cursor> loader, Cursor cursor) { CursorAdapter adapter = getCursorAdapter(loader); if (adapter == null) { return; } adapter.swapCursor(cursor); } @Override public void onAccountsUpdated(Account[] accounts) { populateList(); } void syncStateChanged(boolean syncActive) { Log.v(TAG, String.format("syncStateChanged: %s", syncActive)); getListView().post(new Runnable() { @Override public void run() { mAdapter.notifyDataSetChanged(); } }); } private static final class BankAccountsAdapter extends ResourceCursorAdapter { private BankAccountsAdapter(Context context, int layout, Cursor c, int flags) { super(context, layout, c, flags); } @Override public Cursor swapCursor(Cursor newCursor) { Cursor oldCursor = super.swapCursor(newCursor); Log.d(TAG, "swaping cursor"); if (oldCursor != null) { Log.d(TAG, String.format("old cursor size=%d", oldCursor.getCount())); } if (newCursor != null) { Log.d(TAG, String.format("new cursor size=%d", newCursor.getCount())); } return oldCursor; } @Override public void bindView(View view, Context context, Cursor cursor) { String name = getFromColumn(cursor, COLUMN_NAME_NAME); setToView(view, R.id.name, name); String iban = Convert.formatIBAN(getFromColumn(cursor, COLUMN_NAME_IBAN)); if (TextUtils.equals(name, iban)) { iban = null; } setToView(view, R.id.description, iban); setToView(view, R.id.last_transaction, getFromColumn(cursor, COLUMN_NAME_LAST_TRANSACTION_DATE)); setToView(view, R.id.sum, Convert.formatCurrency(getFromFColumn(cursor, COLUMN_NAME_AVAILABLE_BALANCE), getFromColumn(cursor, COLUMN_NAME_CURRENCY))); } private String getFromColumn(Cursor cursor, String columnName) { int columnIndex = cursor.getColumnIndex(columnName); if (columnIndex != -1) { return cursor.isNull(columnIndex) ? null : cursor.getString(columnIndex); } return null; } private Float getFromFColumn(Cursor cursor, String columnName) { int columnIndex = cursor.getColumnIndex(columnName); if (columnIndex != -1) { return cursor.isNull(columnIndex) ? null : cursor.getFloat(columnIndex); } return null; } private void setToView(View layout, int id, String text) { View view = layout.findViewById(id); if (view == null) { return; } if (!(view instanceof TextView)) { return; } if (TextUtils.isEmpty(text)) { view.setVisibility(View.GONE); } else { view.setVisibility(View.VISIBLE); ((TextView) view).setText(text); } } } private static final class EmptyBankAccountsAdapter extends AdapterWrapper { private Account account; private LayoutInflater layoutInflater; public EmptyBankAccountsAdapter(Context context, Account account, ListAdapter wrapped) { super(wrapped); this.account = account; layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public int getCount() { int size = getWrappedAdapter().getCount(); if (size > 0) { return size; } return 1; } @Override public int getViewTypeCount() { return getWrappedAdapter().getViewTypeCount() + 1; } @Override public int getItemViewType(int position) { if (getWrappedAdapter().getCount() > 0) { return getWrappedAdapter().getItemViewType(position); } else { return getWrappedAdapter().getViewTypeCount(); } } @Override public View getView(int position, View convertView, ViewGroup parent) { if (position == 0 && getWrappedAdapter().getCount() == 0) { View view; if (convertView != null && convertView instanceof TextView) { view = convertView; } else { view = layoutInflater.inflate(R.layout.row_bank_account_status, parent, false); } TextView label = (TextView) view; if (ContentResolver.isSyncActive(account, BankingContract.AUTHORITY)) { label.setText(R.string.status_syncing); } else if (ContentResolver.isSyncPending(account, BankingContract.AUTHORITY)) { label.setText(R.string.status_pending_sync); } else if (((CursorAdapter) getWrappedAdapter()).getCursor() == null) { label.setText(R.string.status_loading); } else { label.setText(R.string.status_no_accounts); } return view; } return super.getView(position, convertView, parent); } } final BroadcastReceiver syncReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (SyncAdapter.START_SYNC.equals(intent.getAction())) { syncStateChanged(true); } else if (SyncAdapter.STOP_SYNC.equals(intent.getAction())) { syncStateChanged(false); } } }; }