Java tutorial
/* * Copyright (c) 2012 - 2015 Ngewi Fet <ngewif@gmail.com> * * 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 org.gnucash.android.ui.homescreen; import android.app.Activity; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.support.v4.widget.SimpleCursorAdapter; import android.support.v7.preference.PreferenceManager; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.Button; import android.widget.CheckBox; import android.widget.ImageButton; import android.widget.RemoteViews; import android.widget.Spinner; import android.widget.Toast; import org.gnucash.android.R; import org.gnucash.android.db.BookDbHelper; import org.gnucash.android.db.DatabaseHelper; import org.gnucash.android.db.DatabaseSchema; import org.gnucash.android.db.adapter.AccountsDbAdapter; import org.gnucash.android.db.adapter.BooksDbAdapter; import org.gnucash.android.model.Account; import org.gnucash.android.model.Book; import org.gnucash.android.model.Money; import org.gnucash.android.receivers.TransactionAppWidgetProvider; import org.gnucash.android.ui.account.AccountsActivity; import org.gnucash.android.ui.common.FormActivity; import org.gnucash.android.ui.common.UxArgument; import org.gnucash.android.ui.settings.PreferenceActivity; import org.gnucash.android.ui.transaction.TransactionsActivity; import org.gnucash.android.util.QualifiedAccountNameCursorAdapter; import java.util.Locale; import java.util.prefs.Preferences; import butterknife.BindView; import butterknife.ButterKnife; /** * Activity for configuration which account to display on a widget. * The activity is opened each time a widget is added to the homescreen * @author Ngewi Fet <ngewif@gmail.com> */ public class WidgetConfigurationActivity extends Activity { private AccountsDbAdapter mAccountsDbAdapter; private int mAppWidgetId; @BindView(R.id.input_accounts_spinner) Spinner mAccountsSpinner; @BindView(R.id.input_books_spinner) Spinner mBooksSpinner; @BindView(R.id.input_hide_account_balance) CheckBox mHideAccountBalance; @BindView(R.id.btn_save) Button mOkButton; @BindView(R.id.btn_cancel) Button mCancelButton; private SimpleCursorAdapter mAccountsCursorAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.widget_configuration); setResult(RESULT_CANCELED); ButterKnife.bind(this); BooksDbAdapter booksDbAdapter = BooksDbAdapter.getInstance(); Cursor booksCursor = booksDbAdapter.fetchAllRecords(); String currentBookUID = booksDbAdapter.getActiveBookUID(); //determine the position of the currently active book in the cursor int position = 0; while (booksCursor.moveToNext()) { String bookUID = booksCursor .getString(booksCursor.getColumnIndexOrThrow(DatabaseSchema.BookEntry.COLUMN_UID)); if (bookUID.equals(currentBookUID)) break; ++position; } SimpleCursorAdapter booksCursorAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_spinner_item, booksCursor, new String[] { DatabaseSchema.BookEntry.COLUMN_DISPLAY_NAME }, new int[] { android.R.id.text1 }, 0); booksCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mBooksSpinner.setAdapter(booksCursorAdapter); mBooksSpinner.setSelection(position); mAccountsDbAdapter = AccountsDbAdapter.getInstance(); Cursor cursor = mAccountsDbAdapter.fetchAllRecordsOrderedByFullName(); if (cursor.getCount() <= 0) { Toast.makeText(this, R.string.error_no_accounts, Toast.LENGTH_LONG).show(); finish(); } mAccountsCursorAdapter = new QualifiedAccountNameCursorAdapter(this, cursor); //without this line, the app crashes when a user tries to select an account mAccountsCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mAccountsSpinner.setAdapter(mAccountsCursorAdapter); boolean passcodeEnabled = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) .getBoolean(UxArgument.ENABLED_PASSCODE, false); mHideAccountBalance.setChecked(passcodeEnabled); bindListeners(); } /** * Sets click listeners for the buttons in the dialog */ private void bindListeners() { mBooksSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Book book = BooksDbAdapter.getInstance().getRecord(id); SQLiteDatabase db = new DatabaseHelper(WidgetConfigurationActivity.this, book.getUID()) .getWritableDatabase(); mAccountsDbAdapter = new AccountsDbAdapter(db); Cursor cursor = mAccountsDbAdapter.fetchAllRecordsOrderedByFullName(); mAccountsCursorAdapter.swapCursor(cursor); mAccountsCursorAdapter.notifyDataSetChanged(); } @Override public void onNothingSelected(AdapterView<?> parent) { //nothing to see here, move along } }); mOkButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = getIntent(); Bundle extras = intent.getExtras(); if (extras != null) { mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); } if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { finish(); return; } String bookUID = BooksDbAdapter.getInstance().getUID(mBooksSpinner.getSelectedItemId()); String accountUID = mAccountsDbAdapter.getUID(mAccountsSpinner.getSelectedItemId()); boolean hideAccountBalance = mHideAccountBalance.isChecked(); configureWidget(WidgetConfigurationActivity.this, mAppWidgetId, bookUID, accountUID, hideAccountBalance); updateWidget(WidgetConfigurationActivity.this, mAppWidgetId); Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); setResult(RESULT_OK, resultValue); finish(); } }); mCancelButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); } /** * Configure a given widget with the given parameters. * @param context The current context * @param appWidgetId ID of the widget to configure * @param bookUID UID of the book for this widget * @param accountUID UID of the account for this widget * @param hideAccountBalance <code>true</code> if the account balance should be hidden, * <code>false</code> otherwise */ public static void configureWidget(final Context context, int appWidgetId, String bookUID, String accountUID, boolean hideAccountBalance) { context.getSharedPreferences("widget:" + appWidgetId, MODE_PRIVATE).edit() .putString(UxArgument.BOOK_UID, bookUID).putString(UxArgument.SELECTED_ACCOUNT_UID, accountUID) .putBoolean(UxArgument.HIDE_ACCOUNT_BALANCE_IN_WIDGET, hideAccountBalance).apply(); } /** * Remove the configuration for a widget. Primarily this should be called when a widget is * destroyed. * @param context The current context * @param appWidgetId ID of the widget whose configuration should be removed */ public static void removeWidgetConfiguration(final Context context, int appWidgetId) { context.getSharedPreferences("widget:" + appWidgetId, MODE_PRIVATE).edit().clear().apply(); } /** * Load obsolete preferences for a widget, if they exist, and save them using the new widget * configuration format. * @param context The current context * @param appWidgetId ID of the widget whose configuration to load/save */ private static void loadOldPreferences(Context context, int appWidgetId) { SharedPreferences preferences = PreferenceActivity.getActiveBookSharedPreferences(); String accountUID = preferences.getString(UxArgument.SELECTED_ACCOUNT_UID + appWidgetId, null); if (accountUID != null) { String bookUID = BooksDbAdapter.getInstance().getActiveBookUID(); boolean hideAccountBalance = preferences .getBoolean(UxArgument.HIDE_ACCOUNT_BALANCE_IN_WIDGET + appWidgetId, false); configureWidget(context, appWidgetId, bookUID, accountUID, hideAccountBalance); preferences.edit().remove(UxArgument.SELECTED_ACCOUNT_UID + appWidgetId) .remove(UxArgument.HIDE_ACCOUNT_BALANCE_IN_WIDGET + appWidgetId).apply(); } } /** * Updates the widget with id <code>appWidgetId</code> with information from the * account with record ID <code>accountId</code> * If the account has been deleted, then a notice is posted in the widget * @param appWidgetId ID of the widget to be updated */ public static void updateWidget(final Context context, int appWidgetId) { Log.i("WidgetConfiguration", "Updating widget: " + appWidgetId); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); loadOldPreferences(context, appWidgetId); SharedPreferences preferences = context.getSharedPreferences("widget:" + appWidgetId, MODE_PRIVATE); String bookUID = preferences.getString(UxArgument.BOOK_UID, null); String accountUID = preferences.getString(UxArgument.SELECTED_ACCOUNT_UID, null); boolean hideAccountBalance = preferences.getBoolean(UxArgument.HIDE_ACCOUNT_BALANCE_IN_WIDGET, false); if (bookUID == null || accountUID == null) { return; } AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(BookDbHelper.getDatabase(bookUID)); final Account account; try { account = accountsDbAdapter.getRecord(accountUID); } catch (IllegalArgumentException e) { Log.i("WidgetConfiguration", "Account not found, resetting widget " + appWidgetId); //if account has been deleted, let the user know RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_4x1); views.setTextViewText(R.id.account_name, context.getString(R.string.toast_account_deleted)); views.setTextViewText(R.id.transactions_summary, ""); //set it to simply open the app PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(context, AccountsActivity.class), 0); views.setOnClickPendingIntent(R.id.widget_layout, pendingIntent); views.setOnClickPendingIntent(R.id.btn_new_transaction, pendingIntent); appWidgetManager.updateAppWidget(appWidgetId, views); Editor editor = PreferenceActivity.getActiveBookSharedPreferences().edit(); //PreferenceManager.getDefaultSharedPreferences(context).edit(); editor.remove(UxArgument.SELECTED_ACCOUNT_UID + appWidgetId); editor.apply(); return; } final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_4x1); views.setTextViewText(R.id.account_name, account.getName()); Money accountBalance = accountsDbAdapter.getAccountBalance(accountUID, -1, System.currentTimeMillis()); if (hideAccountBalance) { views.setViewVisibility(R.id.transactions_summary, View.GONE); } else { views.setTextViewText(R.id.transactions_summary, accountBalance.formattedString(Locale.getDefault())); int color = accountBalance.isNegative() ? R.color.debit_red : R.color.credit_green; views.setTextColor(R.id.transactions_summary, context.getResources().getColor(color)); } Intent accountViewIntent = new Intent(context, TransactionsActivity.class); accountViewIntent.setAction(Intent.ACTION_VIEW); accountViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); accountViewIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID); accountViewIntent.putExtra(UxArgument.BOOK_UID, bookUID); PendingIntent accountPendingIntent = PendingIntent.getActivity(context, appWidgetId, accountViewIntent, 0); views.setOnClickPendingIntent(R.id.widget_layout, accountPendingIntent); if (accountsDbAdapter.isPlaceholderAccount(accountUID)) { views.setOnClickPendingIntent(R.id.btn_view_account, accountPendingIntent); views.setViewVisibility(R.id.btn_new_transaction, View.GONE); } else { Intent newTransactionIntent = new Intent(context, FormActivity.class); newTransactionIntent.setAction(Intent.ACTION_INSERT_OR_EDIT); newTransactionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); newTransactionIntent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.TRANSACTION.name()); newTransactionIntent.putExtra(UxArgument.BOOK_UID, bookUID); newTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID); PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, newTransactionIntent, 0); views.setOnClickPendingIntent(R.id.btn_new_transaction, pendingIntent); views.setViewVisibility(R.id.btn_view_account, View.GONE); } appWidgetManager.updateAppWidget(appWidgetId, views); } /** * Updates all widgets belonging to the application * @param context Application context */ public static void updateAllWidgets(final Context context) { Log.i("WidgetConfiguration", "Updating all widgets"); AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); ComponentName componentName = new ComponentName(context, TransactionAppWidgetProvider.class); final int[] appWidgetIds = widgetManager.getAppWidgetIds(componentName); //update widgets asynchronously so as not to block method which called the update //inside the computation of the account balance new Thread(new Runnable() { @Override public void run() { for (final int widgetId : appWidgetIds) { updateWidget(context, widgetId); } } }).start(); } }