Java tutorial
/* * Copyright (C) 2013 The Android Open Source Project * * 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 net.ddns.mlsoftlaberge.contactslist.ui; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Contacts.Photo; import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.Data; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ListView; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import net.ddns.mlsoftlaberge.contactslist.BuildConfig; import net.ddns.mlsoftlaberge.contactslist.R; import net.ddns.mlsoftlaberge.contactslist.util.ImageLoader; import net.ddns.mlsoftlaberge.contactslist.util.Utils; import java.io.FileNotFoundException; import java.io.IOException; import java.text.DateFormat; import java.text.FieldPosition; import java.text.ParsePosition; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.Vector; import static java.text.DateFormat.*; /** * This fragment displays admins of a specific contact from the contacts provider. It shows the * contact's display photo, name and all its mailing addresses. You can also modify this fragment * to show other information, such as phone numbers, email addresses and so forth. * <p/> * This fragment appears full-screen in an activity on devices with small screen sizes, and as * part of a two-pane layout on devices with larger screens, alongside the * {@link ContactsListFragment}. * <p/> * To create an instance of this fragment, use the factory method * {@link ContactAdminFragment#newInstance(android.net.Uri)}, passing as an argument the contact * Uri for the contact you want to display. */ public class ContactAdminFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> { public static final String EXTRA_CONTACT_URI = "net.ddns.mlsoftlaberge.contactslist.ui.EXTRA_CONTACT_URI"; // Defines a tag for identifying log entries private static final String TAG = "ContactAdminFragment"; private static final int EDITMEMO_REQUEST = 1; private static final int EDITTRANS_REQUEST = 2; // The geo Uri scheme prefix, used with Intent.ACTION_VIEW to form a geographical address // intent that will trigger available apps to handle viewing a location (such as Maps) private static final String GEO_URI_SCHEME_PREFIX = "geo:0,0?q="; private Uri mContactUri; // Stores the contact Uri for this fragment instance private ImageLoader mImageLoader; // Handles loading the contact image in a background thread // Used to store references to key views, layouts and menu items as these need to be updated // in multiple methods throughout this class. private MenuItem mModifContactMenuItem; private ImageView mImageView; private TextView mContactName; private TextView mMemoItem; private ImageButton mMemoEditButton; private ImageButton mAddAdminButton; private LinearLayout mTransactionLayout; private TextView mTransactionTotal; private ImageButton mTransactionSaveButton; private LinearLayout mAddressLayout; private LinearLayout mPhoneLayout; private LinearLayout mEmailLayout; /** * Factory method to generate a new instance of the fragment given a contact Uri. A factory * method is preferable to simply using the constructor as it handles creating the bundle and * setting the bundle as an argument. * * @param contactUri The contact Uri to load * @return A new instance of {@link ContactAdminFragment} */ public static ContactAdminFragment newInstance(Uri contactUri) { // Create new instance of this fragment final ContactAdminFragment fragment = new ContactAdminFragment(); // Create and populate the args bundle final Bundle args = new Bundle(); args.putParcelable(EXTRA_CONTACT_URI, contactUri); // Assign the args bundle to the new fragment fragment.setArguments(args); // Return fragment return fragment; } /** * Fragments require an empty constructor. */ public ContactAdminFragment() { } /** * Sets the contact that this Fragment displays, or clears the display if the contact argument * is null. This will re-initialize all the views and start the queries to the system contacts * provider to populate the contact information. * * @param contactLookupUri The contact lookup Uri to load and display in this fragment. Passing * null is valid and the fragment will display a message that no * contact is currently selected instead. */ public void setContact(Uri contactLookupUri) { // In version 3.0 and later, stores the provided contact lookup Uri in a class field. This // Uri is then used at various points in this class to map to the provided contact. if (Utils.hasHoneycomb()) { mContactUri = contactLookupUri; } else { // For versions earlier than Android 3.0, stores a contact Uri that's constructed from // contactLookupUri. Later on, the resulting Uri is combined with // Contacts.Data.CONTENT_DIRECTORY to map to the provided contact. It's done // differently for these earlier versions because Contacts.Data.CONTENT_DIRECTORY works // differently for Android versions before 3.0. mContactUri = Contacts.lookupContact(getActivity().getContentResolver(), contactLookupUri); } // If the Uri contains data, load the contact's image and load contact admins. if (contactLookupUri != null) { // Asynchronously loads the contact image mImageLoader.loadImage(mContactUri, mImageView); // Shows the contact photo ImageView and hides the empty view mImageView.setVisibility(View.VISIBLE); // Shows the edit contact action/menu item if (mModifContactMenuItem != null) { mModifContactMenuItem.setVisible(true); } // Starts two queries to to retrieve contact information from the Contacts Provider. // restartLoader() is used instead of initLoader() as this method may be called // multiple times. getLoaderManager().restartLoader(ContactDetailQuery.QUERY_ID, null, this); getLoaderManager().restartLoader(ContactAddressQuery.QUERY_ID, null, this); getLoaderManager().restartLoader(ContactNotesQuery.QUERY_ID, null, this); getLoaderManager().restartLoader(ContactPhoneQuery.QUERY_ID, null, this); getLoaderManager().restartLoader(ContactEmailQuery.QUERY_ID, null, this); } else { // If contactLookupUri is null, then the method was called when no contact was selected // in the contacts list. This should only happen in a two-pane layout when the user // hasn't yet selected a contact. Don't display an image for the contact, and don't // account for the view's space in the layout. Turn on the TextView that appears when // the layout is empty, and set the contact name to the empty string. Turn off any menu // items that are visible. mImageView.setVisibility(View.GONE); if (mContactName != null) { mContactName.setText(""); } if (mModifContactMenuItem != null) { mModifContactMenuItem.setVisible(false); } } } /** * When the Fragment is first created, this callback is invoked. It initializes some key * class fields. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Let this fragment contribute menu items setHasOptionsMenu(true); /* * The ImageLoader takes care of loading and resizing images asynchronously into the * ImageView. More thorough sample code demonstrating background image loading as well as * admins on how it works can be found in the following Android Training class: * http://developer.android.com/training/displaying-bitmaps/ */ mImageLoader = new ImageLoader(getActivity(), getLargestScreenDimension()) { @Override protected Bitmap processBitmap(Object data) { // This gets called in a background thread and passed the data from // ImageLoader.loadImage(). return loadContactPhoto((Uri) data, getImageSize()); } }; // Set a placeholder loading image for the image loader mImageLoader.setLoadingImage(R.drawable.ic_contact_picture_180_holo_light); // Tell the image loader to set the image directly when it's finished loading // rather than fading in mImageLoader.setImageFadeIn(false); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflates the main layout to be used by this fragment final View adminView = inflater.inflate(R.layout.contact_admin_fragment, container, false); // Gets handles to view objects in the layout mImageView = (ImageView) adminView.findViewById(R.id.contact_image); mContactName = (TextView) adminView.findViewById(R.id.contact_name); mContactName.setVisibility(View.VISIBLE); // the textview for the memo part of the note mMemoItem = (TextView) adminView.findViewById(R.id.contact_memo_item); // Defines an onClickListener object for the add-admin button mMemoEditButton = (ImageButton) adminView.findViewById(R.id.button_edit_memo); mMemoEditButton.setOnClickListener(new View.OnClickListener() { // Defines what to do when users click the address button @Override public void onClick(View view) { // Displays a message that describe the action to take Toast.makeText(getActivity(), "Edit Notes", Toast.LENGTH_SHORT).show(); editmemo(); } }); // Defines an onClickListener object for the add-admin button mAddAdminButton = (ImageButton) adminView.findViewById(R.id.button_add_admin); mAddAdminButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Displays a message that no activity can handle the view button. Toast.makeText(getActivity(), "Add Admin", Toast.LENGTH_SHORT).show(); addtransaction(); } }); mTransactionLayout = (LinearLayout) adminView.findViewById(R.id.contact_transaction_layout); mTransactionTotal = (TextView) adminView.findViewById(R.id.contact_transaction_total); mTransactionSaveButton = (ImageButton) adminView.findViewById(R.id.button_save_transaction); mTransactionSaveButton.setOnClickListener(new View.OnClickListener() { // Defines what to do when users click the address button @Override public void onClick(View view) { // Displays a message that no activity can handle the view button. Toast.makeText(getActivity(), "Save Transaction", Toast.LENGTH_SHORT).show(); // try to update the note field in database savenote(); } }); mAddressLayout = (LinearLayout) adminView.findViewById(R.id.contact_address_layout); mPhoneLayout = (LinearLayout) adminView.findViewById(R.id.contact_phone_layout); mEmailLayout = (LinearLayout) adminView.findViewById(R.id.contact_email_layout); return adminView; } // open the main address on google-map public void openaddress(String address) { final Intent viewIntent = new Intent(Intent.ACTION_VIEW, constructGeoUri(address)); // A PackageManager instance is needed to verify that there's a default app // that handles ACTION_VIEW and a geo Uri. final PackageManager packageManager = getActivity().getPackageManager(); // Checks for an activity that can handle this intent. Preferred in this // case over Intent.createChooser() as it will still let the user choose // a default (or use a previously set default) for geo Uris. if (packageManager.resolveActivity(viewIntent, PackageManager.MATCH_DEFAULT_ONLY) != null) { startActivity(viewIntent); } else { // If no default is found, displays a message that no activity can handle // the view button. Toast.makeText(getActivity(), R.string.no_intent_found, Toast.LENGTH_SHORT).show(); } } // open the phone-dialer with the phone number pre-dialed public void phonecontact(String phone) { Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phone)); startActivity(intent); } // email the note-transaction part to the contact public void emailcontact(String email) { prepare_invoice(); Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); emailIntent.setType("message/rfc822"); emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] { email }); emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Account State"); emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, noteinvoice.toString()); try { startActivity(Intent.createChooser(emailIntent, "Send Email.")); } catch (Exception ex) { Toast.makeText(getActivity(), "Email Error", Toast.LENGTH_SHORT).show(); } } // start the editing activity for the memo part private void editmemo() { // start an activity to edit the memo Intent intent = new Intent(getActivity(), ContactEditMemoActivity.class); intent.setData(mContactUri); intent.putExtra("NAME", mContactName.getText().toString()); intent.putExtra("MEMO", notememo.toString()); startActivityForResult(intent, EDITMEMO_REQUEST); } // start the editing activity for a new transaction private void addtransaction() { // create an empty default transaction transac = new Transac(); transac.contactid = mContactId; transac.rawcontactid = mRawContactId; transac.qte = 1.0; transac.prix = 0.0; transac.mnt = 0.0; transac.trdate = ""; transac.amount = ""; transac.descrip = ""; // reformat the date from the date/time now transac.fdate = new Date(); transac.trdate = dateToString(transac.fdate); // add element to the table transaclist.addElement(transac); nbtransac++; edittransaction(nbtransac - 1); } // start the editing activity for editing a transaction private void edittransaction(int i) { transac = transaclist.elementAt(i); curtransac = i; // start an activity to edit the transaction Intent intent = new Intent(getActivity(), ContactEditTransActivity.class); intent.setData(mContactUri); intent.putExtra("NAME", mContactName.getText().toString()); intent.putExtra("DESCRIP", transac.descrip); intent.putExtra("AMOUNT", transac.amount); intent.putExtra("DATE", transac.trdate); startActivityForResult(intent, EDITTRANS_REQUEST); } // capture the results of editing activities, and process them // save the resulting data when needed @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { // If the request went well (OK) and the request was EDITMEMO_REQUEST if (resultCode == Activity.RESULT_OK && requestCode == EDITMEMO_REQUEST) { notememo.setLength(0); notememo.append(data.getStringExtra("MEMO")); mMemoItem.setText(notememo); savenote(); } // If the request went well (OK) and the request was EDITTRANS_REQUEST if (resultCode == Activity.RESULT_OK && requestCode == EDITTRANS_REQUEST) { transac = transaclist.elementAt(curtransac); if (data.getStringExtra("DESCRIP").isEmpty() && data.getStringExtra("AMOUNT").isEmpty()) { transaclist.removeElementAt(curtransac); nbtransac--; } else { transac.descrip = data.getStringExtra("DESCRIP"); transac.amount = data.getStringExtra("AMOUNT"); transac.trdate = data.getStringExtra("DATE"); // reformat the amount transac.mnt = Double.valueOf(transac.amount); transac.prix = transac.mnt; transac.amount = String.format("%.2f", transac.mnt); // reformat the date transac.fdate = stringToDate(transac.trdate); transac.trdate = dateToString(transac.fdate); } compactnote(); savenote(); } } // set the contact URI with the new activity or saved activity state @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // If not being created from a previous state if (savedInstanceState == null) { // Sets the argument extra as the currently displayed contact setContact(getArguments() != null ? (Uri) getArguments().getParcelable(EXTRA_CONTACT_URI) : null); } else { // If being recreated from a saved state, sets the contact from the incoming // savedInstanceState Bundle setContact((Uri) savedInstanceState.getParcelable(EXTRA_CONTACT_URI)); } } /** * When the Fragment is being saved in order to change activity state, save the * currently-selected contact. */ @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // Saves the contact Uri outState.putParcelable(EXTRA_CONTACT_URI, mContactUri); } // open the edit-contact activity when asked by the top menu option @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { // When "edit" menu option selected case R.id.menu_modif_contact: // Standard system edit contact intent Intent intent = new Intent(Intent.ACTION_EDIT, mContactUri); // Because of an issue in Android 4.0 (API level 14), clicking Done or Back in the // People app doesn't return the user to your app; instead, it displays the People // app's contact list. A workaround, introduced in Android 4.0.3 (API level 15) is // to set a special flag in the extended data for the Intent you send to the People // app. The issue is does not appear in versions prior to Android 4.0. You can use // the flag with any version of the People app; if the workaround isn't needed, // the flag is ignored. intent.putExtra("finishActivityOnSaveCompleted", true); // Start the edit activity startActivity(intent); return true; } return super.onOptionsItemSelected(item); } // create the menu option to modify this contact @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); // Inflates the options menu for this fragment inflater.inflate(R.menu.contact_admin_menu, menu); // Gets a handle to the "find" menu item mModifContactMenuItem = menu.findItem(R.id.menu_modif_contact); // If contactUri is null the edit menu item should be hidden, otherwise // it is visible. mModifContactMenuItem.setVisible(mContactUri != null); } // create a loader to find the results of the contact URI for every querys @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { switch (id) { // Two main queries to load the required information case ContactDetailQuery.QUERY_ID: // This query loads main contact admins, see // ContactDetailQuery for more information. return new CursorLoader(getActivity(), mContactUri, ContactDetailQuery.PROJECTION, null, null, null); case ContactAddressQuery.QUERY_ID: // This query loads contact address admins, see // ContactAddressQuery for more information. final Uri uri = Uri.withAppendedPath(mContactUri, Contacts.Data.CONTENT_DIRECTORY); return new CursorLoader(getActivity(), uri, ContactAddressQuery.PROJECTION, ContactAddressQuery.SELECTION, null, null); case ContactNotesQuery.QUERY_ID: // This query loads contact address admins, see // ContactAddressQuery for more information. final Uri nuri = Uri.withAppendedPath(mContactUri, Contacts.Data.CONTENT_DIRECTORY); return new CursorLoader(getActivity(), nuri, ContactNotesQuery.PROJECTION, ContactNotesQuery.SELECTION, null, null); case ContactPhoneQuery.QUERY_ID: // This query loads contact address admins, see // ContactAddressQuery for more information. final Uri puri = Uri.withAppendedPath(mContactUri, Contacts.Data.CONTENT_DIRECTORY); return new CursorLoader(getActivity(), puri, ContactPhoneQuery.PROJECTION, ContactPhoneQuery.SELECTION, null, null); case ContactEmailQuery.QUERY_ID: // This query loads contact address admins, see // ContactAddressQuery for more information. final Uri euri = Uri.withAppendedPath(mContactUri, Contacts.Data.CONTENT_DIRECTORY); return new CursorLoader(getActivity(), euri, ContactEmailQuery.PROJECTION, ContactEmailQuery.SELECTION, null, null); } return null; } // reset the loader @Override public void onLoaderReset(Loader<Cursor> loader) { // Nothing to do here. The Cursor does not need to be released as it was never directly // bound to anything (like an adapter). } // process the loader finished request // fill all fields on screen, and all layouts with data from the querys @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // If this fragment was cleared while the query was running // eg. from from a call like setContact(uri) then don't do // anything. if (mContactUri == null) { return; } // Each LinearLayout has the same LayoutParams so this can // be created once and used for each cumulative layouts of data final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); switch (loader.getId()) { case ContactDetailQuery.QUERY_ID: // Moves to the first row in the Cursor if (data.moveToFirst()) { // For the contact admins query, fetches the contact display name. // ContactDetailQuery.DISPLAY_NAME maps to the appropriate display // name field based on OS version. final String contactName = data.getString(ContactDetailQuery.DISPLAY_NAME); mContactId = data.getString(ContactDetailQuery.ID); mRawContactId = data.getString(ContactDetailQuery.RAWID); if (mContactName != null) { mContactName.setText(contactName); } // In the single pane layout, sets the activity title // to the contact name. On HC+ this will be set as // the ActionBar title text. getActivity().setTitle(contactName); } break; case ContactAddressQuery.QUERY_ID: // Clears out the details layout first in case the details // layout has addresses from a previous data load still // added as children. mAddressLayout.removeAllViews(); // This query loads the contact address . // Loops through all the rows in the Cursor if (data.moveToFirst()) { // loop thru all addresses for the bottom layout do { // Builds the address layout final LinearLayout alayout = buildAddressLayout(data.getInt(ContactAddressQuery.TYPE), data.getString(ContactAddressQuery.LABEL), data.getString(ContactAddressQuery.ADDRESS)); // Adds the new address layout to the addresses layout mAddressLayout.addView(alayout, layoutParams); } while (data.moveToNext()); } break; case ContactPhoneQuery.QUERY_ID: // Clears out the details layout first in case the details // layout has data from a previous data load still // added as children. mPhoneLayout.removeAllViews(); // This query loads the contact phone // Loops through all the rows in the Cursor if (data.moveToFirst()) { // loop thru all phones for the bottom layout do { final LinearLayout playout = buildPhoneLayout(data.getInt(ContactPhoneQuery.TYPE), data.getString(ContactPhoneQuery.LABEL), data.getString(ContactPhoneQuery.PHONE)); // Adds the new phone layout to the phones layout mPhoneLayout.addView(playout, layoutParams); } while (data.moveToNext()); } break; case ContactEmailQuery.QUERY_ID: // Clears out the details layout first in case the details // layout has data from a previous data load still // added as children. mEmailLayout.removeAllViews(); // This query loads the contact email // Loops through all the rows in the Cursor if (data.moveToFirst()) { // loop thru all emails for the bottom layout do { final LinearLayout elayout = buildEmailLayout(data.getInt(ContactEmailQuery.TYPE), data.getString(ContactEmailQuery.LABEL), data.getString(ContactEmailQuery.EMAIL)); // Adds the new address layout to the details layout mEmailLayout.addView(elayout, layoutParams); // store full note, and process it } while (data.moveToNext()); } break; case ContactNotesQuery.QUERY_ID: // This query loads the contact notes // Get the first row of the cursor (table contains only one row) if (data.moveToFirst()) { // store full note, and process it mNotesData = data.getString(ContactNotesQuery.NOTE); mNotesRawId = data.getString(ContactNotesQuery.RAWID); mNotesId = data.getString(ContactNotesQuery.ID); expandnote(); compactnote(); } else { // If nothing found, clear the data mNotesData = ""; mNotesRawId = ""; mNotesId = ""; clearnote(); } // display the memo part of the note in the memo field (decoded note) mMemoItem.setText(notememo); // fill the layout of editables transactions filltransactionlayout(); break; } } // fill the transactions layout with the transaction views private void filltransactionlayout() { // Each LinearLayout has the same LayoutParams so this can // be created once and used for each cumulative layouts of data final LinearLayout.LayoutParams tlayoutParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); // Clears out the details layout first in case the details // layout has data from a previous data load still // added as children. mTransactionLayout.removeAllViews(); int i; double tot = 0.0; for (i = 0; i < nbtransac; ++i) { // Builds the transaction layout // Inflates the transaction layout LinearLayout tlayout = (LinearLayout) LayoutInflater.from(getActivity()) .inflate(R.layout.contact_transaction_item, null, false); // point to the 4 fields of the layout TextView t = (TextView) tlayout.findViewById(R.id.contact_transaction_description); TextView dt = (TextView) tlayout.findViewById(R.id.contact_transaction_date); TextView mnt = (TextView) tlayout.findViewById(R.id.contact_transaction_amount); ImageButton butt = (ImageButton) tlayout.findViewById(R.id.button_edit_transaction); // get the current transaction transac = transaclist.elementAt(i); // fill the fields with the table data t.setText(transac.descrip); dt.setText(transac.trdate); mnt.setText(transac.amount); // cumulate the total amount tot += transac.mnt; //save the position of the line in button id butt.setId(i); // Defines an onClickListener object for the address button butt.setOnClickListener(new View.OnClickListener() { // Defines what to do when users click the address button @Override public void onClick(View view) { // Displays a message that no activity can handle the view button. Toast.makeText(getActivity(), "Edit Transaction " + view.getId(), Toast.LENGTH_SHORT).show(); edittransaction(view.getId()); } }); // Adds the new note layout to the notes layout mTransactionLayout.addView(tlayout, tlayoutParams); } // stamp the total amount in bottom view after the transaction layout String stot = String.format("%.2f", tot); mTransactionTotal.setText(stot); } // try to update the note field in database, or insert it if new public void savenote() { compactnote(); if (mNotesRawId.isEmpty()) { mNotesRawId = mRawContactId; mNotesId = mContactId; insertnote(); } else { updatenote(); } } // normalize the memo field, so the last line finish with a newline private void normalizememo() { if (notememo.length() == 0) return; if (notememo.charAt(notememo.length() - 1) != '\n') notememo.append("\n"); } // update the note record in the database from the modified data in table private void updatenote() { normalizememo(); newnote = notememo.toString() + notereformat.toString(); // update the record try { ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + " = ?", new String[] { mNotesRawId }) .withSelection(Data._ID + " = ?", new String[] { mNotesId }) .withValue(Data.MIMETYPE, ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE) .withValue(Data.DATA1, newnote).build()); getActivity().getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); // inform of success Toast.makeText(getActivity(), "Transaction Updated", Toast.LENGTH_SHORT).show(); } catch (Exception e) { Toast.makeText(getActivity(), "Transaction Not Updated", Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_SHORT).show(); } } // insert a new note in the database public void insertnote() { normalizememo(); newnote = notememo.toString() + notereformat.toString(); try { ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI).withValue(Data.RAW_CONTACT_ID, mNotesRawId) .withValue(Data.MIMETYPE, ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE) .withValue(Data.DATA1, newnote).build()); getActivity().getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); Toast.makeText(getActivity(), "Transaction Inserted", Toast.LENGTH_SHORT).show(); } catch (Exception e) { Toast.makeText(getActivity(), "Transaction Not Inserted", Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_SHORT).show(); } } // -------------------------------------------------------------------- // this is the decoding/encoding part of the transaction table // -------------------------------------------------------------------- // feed string user by the note decoder private String mNotesData = ""; // from notes row private String mNotesRawId = ""; // from notes row private String mNotesId = ""; // from notes row private String mContactId = ""; // from contact name private String mRawContactId = ""; // from contact name // string containing the memo part of the note private StringBuffer notememo = new StringBuffer(); // string containing the reformatted part of the note private StringBuffer notereformat = new StringBuffer(); // string containing an invoice list in human readable form private StringBuffer noteinvoice = new StringBuffer(); // string containing the full note reassembled and ready to write back on disk private String newnote; // temporary space for line in treatment private String noteline = ""; // structure of a row of the table private class Transac { String contactid; String rawcontactid; double qte; double prix; double mnt; Date fdate; String trdate; String amount; String descrip; } // tables containing the expanded note data private Vector<Transac> transaclist = new Vector<Transac>(10, 10); // one row of the table private Transac transac; // counter of table row private int nbtransac = 0; // current table row in edition private int curtransac = 0; // clear the table of fields for an empty note private void clearnote() { nbtransac = 0; notememo.setLength(0); transaclist.removeAllElements(); } // expand the note in a table of fields // scan mNotesData and cut it in fields and tables private void expandnote() { clearnote(); // expand note in fields int i = 0; int p; while (i < mNotesData.length()) { // find the position of the end of line (may be end of block too) p = mNotesData.indexOf('\n', i); if (p < 0) { p = mNotesData.length() - 1; } noteline = mNotesData.substring(i, p + 1); // if an admin line, then decode it // else add it to the memo field if (noteline.indexOf("ADMIN|") == 0) { decodeline(); } else { notememo.append(noteline); } // advance after the last char eated by line i = p + 1; } } // decode the noteline beginning by ADMIN| and add it to the table private void decodeline() { // create an empty default transaction transac = new Transac(); transac.contactid = mNotesId; transac.rawcontactid = mNotesRawId; transac.qte = 1.0; transac.prix = 0.0; transac.mnt = 0.0; transac.trdate = "20160101"; transac.amount = "0.00"; transac.descrip = noteline; // decode the line int i = 6; int p; // search for the date p = noteline.indexOf('|', i); if (p < 0) p = noteline.length() - 1; if (p >= i) { transac.trdate = noteline.substring(i, p); i = p + 1; // search for the amount p = noteline.indexOf('|', i); if (p < 0) p = noteline.length() - 1; if (p >= i) { transac.amount = noteline.substring(i, p); i = p + 1; // search for the description p = noteline.indexOf('|', i); if (p < 0) p = noteline.indexOf('\n', i); if (p < 0) p = noteline.length(); if (p >= i) { transac.descrip = noteline.substring(i, p); i = p + 1; } } } // reformat the amount transac.mnt = Double.valueOf(transac.amount); transac.prix = transac.mnt; transac.amount = String.format("%.2f", transac.mnt); // reformat the date transac.fdate = stringToDate(transac.trdate); transac.trdate = dateToString(transac.fdate); // add element to the table transaclist.addElement(transac); nbtransac++; } // transform the transaction table in a memo form, and display it in the last bottom debug field private void prepare_invoice() { int i; double tot = 0.0; noteinvoice.setLength(0); // refill the string with the transactions list in readable mode noteinvoice.append("<html><PRE>\n+-----------+---------+------------+-------------------------------+\n"); for (i = 0; i < nbtransac; ++i) { transac = transaclist.elementAt(i); noteinvoice.append("| "); noteinvoice.append(transac.trdate); noteinvoice.append(" | "); noteinvoice.append(String.format("%10.2f", transac.mnt)); noteinvoice.append(" | "); noteinvoice.append(String.format("%-30.30s", transac.descrip)); noteinvoice.append(" |\n"); tot += transac.mnt; } noteinvoice.append("+-----------+---------+------------+-------------------------------+\n"); noteinvoice .append(String.format("+ TOTAL + %10.2f + +\n", tot)); noteinvoice.append("+-----------+---------+------------+-------------------------------+\n</PRE></html>"); } // transform the transaction table in a memo form, and display it in the last bottom debug field private void compactnote() { int i; notereformat.setLength(0); // refill the string with the transactions list in readable mode for (i = 0; i < nbtransac; ++i) { transac = transaclist.elementAt(i); notereformat.append("ADMIN|"); notereformat.append(transac.trdate); notereformat.append("|"); notereformat.append(transac.amount); notereformat.append("|"); notereformat.append(transac.descrip); notereformat.append("\n"); } } private String onlyNumbers(String from) { StringBuffer tonum = new StringBuffer(); tonum.setLength(14); int i; int j = 0; for (i = 0; i < from.length(); ++i) { char c = from.charAt(i); if (c >= '0' && c <= '9') { tonum.setCharAt(j, c); j++; } } tonum.setLength(j); return tonum.toString(); } // convert a string to a date field private Date stringToDate(String sdate) { // prepare the date parser. Calendar cal = Calendar.getInstance(); int year, month, day, hour, min, sec; // extract only numbers from the string received String ndate = onlyNumbers(sdate); // extract all subvalues if (ndate.length() >= 4) year = Integer.valueOf(ndate.substring(0, 4)); else year = 0; if (ndate.length() >= 6) month = Integer.valueOf(ndate.substring(4, 6)); else month = 1; if (ndate.length() >= 8) day = Integer.valueOf(ndate.substring(6, 8)); else day = 0; if (ndate.length() >= 10) hour = Integer.valueOf(ndate.substring(8, 10)); else hour = 0; if (ndate.length() >= 12) min = Integer.valueOf(ndate.substring(10, 12)); else min = 0; if (ndate.length() >= 14) sec = Integer.valueOf(ndate.substring(12, 14)); else sec = 0; // use calendar to format date/time cal.set(year, month - 1, day, hour, min, sec); long ldate = cal.getTimeInMillis(); return new Date(ldate); } // convert a date field to a readable string private String dateToString(Date date) { // prepare the date parser. Calendar cal = Calendar.getInstance(); cal.setTime(date); int year, month, day, hour, min, sec; year = cal.get(Calendar.YEAR); month = cal.get(Calendar.MONTH) + 1; day = cal.get(Calendar.DAY_OF_MONTH); hour = cal.get(Calendar.HOUR_OF_DAY); min = cal.get(Calendar.MINUTE); sec = cal.get(Calendar.SECOND); return String.format("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec); } /** * Builds a notes LinearLayout based on note information from the Contacts Provider. * Each note for the contact gets its own LinearLayout object; for example, if the contact * has three notes, then 3 LinearLayouts are generated. * * @param type From * {@link android.provider.ContactsContract.CommonDataKinds.Phone#TYPE} * @param label From * {@link android.provider.ContactsContract.CommonDataKinds.Phone#LABEL} * @param phone From * {@link android.provider.ContactsContract.CommonDataKinds.Phone#NUMBER} * @return A LinearLayout to add to the contact notes layout, * populated with the provided notes. */ private LinearLayout buildPhoneLayout(final int type, final String label, final String phone) { // Inflates the address layout final LinearLayout phoneLayout = (LinearLayout) LayoutInflater.from(getActivity()) .inflate(R.layout.contact_phone_item, mPhoneLayout, false); // Gets handles to the view objects in the layout final TextView pheaderTextView = (TextView) phoneLayout.findViewById(R.id.contact_phone_header); final TextView phoneTextView = (TextView) phoneLayout.findViewById(R.id.contact_phone_item); final ImageButton editPhoneButton = (ImageButton) phoneLayout.findViewById(R.id.button_edit_phone); // If there's no addresses for the contact, shows the empty view and message, and hides the // header and button. if (phone == null) { pheaderTextView.setVisibility(View.GONE); editPhoneButton.setVisibility(View.GONE); phoneTextView.setText(""); } else { // Gets postal address label type CharSequence plabel = android.provider.ContactsContract.CommonDataKinds.Phone .getTypeLabel(getResources(), type, label); // Sets TextView objects in the layout pheaderTextView.setText(plabel + " Phone"); phoneTextView.setText(phone); editPhoneButton.setContentDescription(phone); // Defines an onClickListener object for the address button editPhoneButton.setOnClickListener(new View.OnClickListener() { // Defines what to do when users click the address button @Override public void onClick(View view) { // Displays a message that no activity can handle the view button. Toast.makeText(getActivity(), "Call Phone", Toast.LENGTH_SHORT).show(); phonecontact(view.getContentDescription().toString()); } }); } return phoneLayout; } /** * Builds a notes LinearLayout based on note information from the Contacts Provider. * Each note for the contact gets its own LinearLayout object; for example, if the contact * has three notes, then 3 LinearLayouts are generated. * * @param type From * {@link android.provider.ContactsContract.CommonDataKinds.Email#TYPE} * @param label From * {@link android.provider.ContactsContract.CommonDataKinds.Email#LABEL} * @param email From * {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} * @return A LinearLayout to add to the contact notes layout, * populated with the provided notes. */ private LinearLayout buildEmailLayout(final int type, final String label, final String email) { // Inflates the address layout final LinearLayout emailLayout = (LinearLayout) LayoutInflater.from(getActivity()) .inflate(R.layout.contact_email_item, mEmailLayout, false); // Gets handles to the view objects in the layout final TextView eheaderTextView = (TextView) emailLayout.findViewById(R.id.contact_email_header); final TextView emailTextView = (TextView) emailLayout.findViewById(R.id.contact_email_item); final ImageButton editEmailButton = (ImageButton) emailLayout.findViewById(R.id.button_edit_email); // If there's no addresses for the contact, shows the empty view and message, and hides the // header and button. if (email == null) { eheaderTextView.setVisibility(View.GONE); editEmailButton.setVisibility(View.GONE); emailTextView.setText(""); } else { // Gets postal address label type CharSequence elabel = android.provider.ContactsContract.CommonDataKinds.Email .getTypeLabel(getResources(), type, label); // Sets TextView objects in the layout eheaderTextView.setText(elabel + " Email"); emailTextView.setText(email); editEmailButton.setContentDescription(email); // Defines an onClickListener object for the address button editEmailButton.setOnClickListener(new View.OnClickListener() { // Defines what to do when users click the address button @Override public void onClick(View view) { // Displays a message that no activity can handle the view button. Toast.makeText(getActivity(), "Send Email", Toast.LENGTH_SHORT).show(); emailcontact(view.getContentDescription().toString()); } }); } return emailLayout; } /** * Builds an address LinearLayout based on address information from the Contacts Provider. * Each address for the contact gets its own LinearLayout object; for example, if the contact * has three postal addresses, then 3 LinearLayouts are generated. * * @param addressType From * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#TYPE} * @param addressTypeLabel From * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#LABEL} * @param address From * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#FORMATTED_ADDRESS} * @return A LinearLayout to add to the contact details layout, * populated with the provided address details. */ private LinearLayout buildAddressLayout(int addressType, String addressTypeLabel, final String address) { // Inflates the address layout final LinearLayout addressLayout = (LinearLayout) LayoutInflater.from(getActivity()) .inflate(R.layout.contact_address_item, mAddressLayout, false); // Gets handles to the view objects in the layout final TextView headerTextView = (TextView) addressLayout.findViewById(R.id.contact_address_header); final TextView addressTextView = (TextView) addressLayout.findViewById(R.id.contact_address_full); final ImageButton viewAddressButton = (ImageButton) addressLayout.findViewById(R.id.button_view_address); // If there's no addresses for the contact, shows the empty view and message, and hides the // header and button. if (addressTypeLabel == null && addressType == 0) { headerTextView.setVisibility(View.GONE); viewAddressButton.setVisibility(View.GONE); addressTextView.setText(R.string.no_address); } else { // Gets postal address label type CharSequence label = StructuredPostal.getTypeLabel(getResources(), addressType, addressTypeLabel); // Sets TextView objects in the layout headerTextView.setText(label + " Address"); addressTextView.setText(address); viewAddressButton.setContentDescription(address); // Defines an onClickListener object for the address button viewAddressButton.setOnClickListener(new View.OnClickListener() { // Defines what to do when users click the address button @Override public void onClick(View view) { final Intent viewIntent = new Intent(Intent.ACTION_VIEW, constructGeoUri(view.getContentDescription().toString())); // A PackageManager instance is needed to verify that there's a default app // that handles ACTION_VIEW and a geo Uri. final PackageManager packageManager = getActivity().getPackageManager(); // Checks for an activity that can handle this intent. Preferred in this // case over Intent.createChooser() as it will still let the user choose // a default (or use a previously set default) for geo Uris. if (packageManager.resolveActivity(viewIntent, PackageManager.MATCH_DEFAULT_ONLY) != null) { // Toast.makeText(getActivity(), // R.string.yes_intent_found, Toast.LENGTH_SHORT).show(); startActivity(viewIntent); } else { // If no default is found, displays a message that no activity can handle // the view button. Toast.makeText(getActivity(), R.string.no_intent_found, Toast.LENGTH_SHORT).show(); } } }); } return addressLayout; } /** * Constructs a geo scheme Uri from a postal address. * * @param postalAddress A postal address. * @return the geo:// Uri for the postal address. */ private Uri constructGeoUri(String postalAddress) { // Concatenates the geo:// prefix to the postal address. The postal address must be // converted to Uri format and encoded for special characters. return Uri.parse(GEO_URI_SCHEME_PREFIX + Uri.encode(postalAddress)); } /** * Fetches the width or height of the screen in pixels, whichever is larger. This is used to * set a maximum size limit on the contact photo that is retrieved from the Contacts Provider. * This limit prevents the app from trying to decode and load an image that is much larger than * the available screen area. * * @return The largest screen dimension in pixels. */ private int getLargestScreenDimension() { // Gets a DisplayMetrics object, which is used to retrieve the display's pixel height and // width final DisplayMetrics displayMetrics = new DisplayMetrics(); // Retrieves a displayMetrics object for the device's default display getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); final int height = displayMetrics.heightPixels; final int width = displayMetrics.widthPixels; // Returns the larger of the two values return height > width ? height : width; } /** * Decodes and returns the contact's thumbnail image. * * @param contactUri The Uri of the contact containing the image. * @param imageSize The desired target width and height of the output image in pixels. * @return If a thumbnail image exists for the contact, a Bitmap image, otherwise null. */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) private Bitmap loadContactPhoto(Uri contactUri, int imageSize) { // Ensures the Fragment is still added to an activity. As this method is called in a // background thread, there's the possibility the Fragment is no longer attached and // added to an activity. If so, no need to spend resources loading the contact photo. if (!isAdded() || getActivity() == null) { return null; } // Instantiates a ContentResolver for retrieving the Uri of the image final ContentResolver contentResolver = getActivity().getContentResolver(); // Instantiates an AssetFileDescriptor. Given a content Uri pointing to an image file, the // ContentResolver can return an AssetFileDescriptor for the file. AssetFileDescriptor afd = null; if (Utils.hasICS()) { // On platforms running Android 4.0 (API version 14) and later, a high resolution image // is available from Photo.DISPLAY_PHOTO. try { // Constructs the content Uri for the image Uri displayImageUri = Uri.withAppendedPath(contactUri, Photo.DISPLAY_PHOTO); // Retrieves an AssetFileDescriptor from the Contacts Provider, using the // constructed Uri afd = contentResolver.openAssetFileDescriptor(displayImageUri, "r"); // If the file exists if (afd != null) { // Reads and decodes the file to a Bitmap and scales it to the desired size return ImageLoader.decodeSampledBitmapFromDescriptor(afd.getFileDescriptor(), imageSize, imageSize); } } catch (FileNotFoundException e) { // Catches file not found exceptions if (BuildConfig.DEBUG) { // Log debug message, this is not an error message as this exception is thrown // when a contact is legitimately missing a contact photo (which will be quite // frequently in a long contacts list). Log.d(TAG, "Contact photo not found for contact " + contactUri.toString() + ": " + e.toString()); } } finally { // Once the decode is complete, this closes the file. You must do this each time // you access an AssetFileDescriptor; otherwise, every image load you do will open // a new descriptor. if (afd != null) { try { afd.close(); } catch (IOException e) { // Closing a file descriptor might cause an IOException if the file is // already closed. Nothing extra is needed to handle this. } } } } // If the platform version is less than Android 4.0 (API Level 14), use the only available // image URI, which points to a normal-sized image. try { // Constructs the image Uri from the contact Uri and the directory twig from the // Contacts.Photo table Uri imageUri = Uri.withAppendedPath(contactUri, Photo.CONTENT_DIRECTORY); // Retrieves an AssetFileDescriptor from the Contacts Provider, using the constructed // Uri afd = getActivity().getContentResolver().openAssetFileDescriptor(imageUri, "r"); // If the file exists if (afd != null) { // Reads the image from the file, decodes it, and scales it to the available screen // area return ImageLoader.decodeSampledBitmapFromDescriptor(afd.getFileDescriptor(), imageSize, imageSize); } } catch (FileNotFoundException e) { // Catches file not found exceptions if (BuildConfig.DEBUG) { // Log debug message, this is not an error message as this exception is thrown // when a contact is legitimately missing a contact photo (which will be quite // frequently in a long contacts list). Log.d(TAG, "Contact photo not found for contact " + contactUri.toString() + ": " + e.toString()); } } finally { // Once the decode is complete, this closes the file. You must do this each time you // access an AssetFileDescriptor; otherwise, every image load you do will open a new // descriptor. if (afd != null) { try { afd.close(); } catch (IOException e) { // Closing a file descriptor might cause an IOException if the file is // already closed. Ignore this. } } } // If none of the case selectors match, returns null. return null; } /** * This interface defines constants used by contact retrieval queries. */ public interface ContactDetailQuery { // A unique query ID to distinguish queries being run by the // LoaderManager. final static int QUERY_ID = 1; // The query projection (columns to fetch from the provider) @SuppressLint("InlinedApi") final static String[] PROJECTION = { Contacts._ID, Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME, Contacts.STARRED, Contacts.LOOKUP_KEY, Contacts.PHOTO_URI, Contacts.NAME_RAW_CONTACT_ID, }; // The query column numbers which map to each value in the projection final static int ID = 0; final static int DISPLAY_NAME = 1; final static int STARRED = 2; final static int LOOKUP_KEY = 3; final static int PHOTO_URI = 4; final static int RAWID = 5; } /** * This interface defines constants used by address retrieval queries. */ public interface ContactAddressQuery { // A unique query ID to distinguish queries being run by the // LoaderManager. final static int QUERY_ID = 2; // The query projection (columns to fetch from the provider) final static String[] PROJECTION = { StructuredPostal._ID, StructuredPostal.FORMATTED_ADDRESS, StructuredPostal.TYPE, StructuredPostal.LABEL, }; // The query selection criteria. In this case matching against the // StructuredPostal content mime type. final static String SELECTION = Data.MIMETYPE + "='" + StructuredPostal.CONTENT_ITEM_TYPE + "'"; // The query column numbers which map to each value in the projection final static int ID = 0; final static int ADDRESS = 1; final static int TYPE = 2; final static int LABEL = 3; } /** * This interface defines constants used by address retrieval queries. */ public interface ContactNotesQuery { // A unique query ID to distinguish queries being run by the // LoaderManager. final static int QUERY_ID = 3; // The query projection (columns to fetch from the provider) final static String[] PROJECTION = { ContactsContract.CommonDataKinds.Note._ID, ContactsContract.CommonDataKinds.Note.NOTE, ContactsContract.CommonDataKinds.Note.RAW_CONTACT_ID, }; // The query selection criteria. In this case matching against the // Note content mime type. final static String SELECTION = Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE + "'"; // The query column numbers which map to each value in the projection final static int ID = 0; final static int NOTE = 1; final static int RAWID = 2; } /** * This interface defines constants used by address retrieval queries. */ public interface ContactPhoneQuery { // A unique query ID to distinguish queries being run by the // LoaderManager. final static int QUERY_ID = 4; // The query projection (columns to fetch from the provider) final static String[] PROJECTION = { ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.LABEL, }; // The query selection criteria. In this case matching against the // Note content mime type. final static String SELECTION = Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "'"; // The query column numbers which map to each value in the projection final static int ID = 0; final static int PHONE = 1; final static int TYPE = 2; final static int LABEL = 3; } /** * This interface defines constants used by address retrieval queries. */ public interface ContactEmailQuery { // A unique query ID to distinguish queries being run by the // LoaderManager. final static int QUERY_ID = 5; // The query projection (columns to fetch from the provider) final static String[] PROJECTION = { ContactsContract.CommonDataKinds.Email._ID, ContactsContract.CommonDataKinds.Email.ADDRESS, ContactsContract.CommonDataKinds.Email.TYPE, ContactsContract.CommonDataKinds.Email.LABEL, }; // The query selection criteria. In this case matching against the // Note content mime type. final static String SELECTION = Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE + "'"; // The query column numbers which map to each value in the projection final static int ID = 0; final static int EMAIL = 1; final static int TYPE = 2; final static int LABEL = 3; } }