1 /* 2 * Copyright 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.system.runtimepermissions.contacts; 18 19 import com.example.android.common.logger.Log; 20 import com.example.android.system.runtimepermissions.R; 21 22 import android.content.ContentProviderOperation; 23 import android.content.ContentResolver; 24 import android.content.OperationApplicationException; 25 import android.database.Cursor; 26 import android.os.Bundle; 27 import android.os.RemoteException; 28 import android.provider.ContactsContract; 29 import android.support.annotation.Nullable; 30 import android.support.v4.app.Fragment; 31 import android.support.v4.app.LoaderManager; 32 import android.support.v4.content.CursorLoader; 33 import android.support.v4.content.Loader; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.widget.Button; 38 import android.widget.TextView; 39 40 import java.util.ArrayList; 41 42 /** 43 * Displays the first contact stored on the device and contains an option to add a dummy contact. 44 * <p> 45 * This Fragment is only used to illustrate that access to the Contacts ContentProvider API has 46 * been granted (or denied) as part of the runtime permissions model. It is not relevant for the 47 * use 48 * of the permissions API. 49 * <p> 50 * This fragments demonstrates a basic use case for accessing the Contacts Provider. The 51 * implementation is based on the training guide available here: 52 * https://developer.android.com/training/contacts-provider/retrieve-names.html 53 */ 54 public class ContactsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> { 55 56 private static final String TAG = "Contacts"; 57 private TextView mMessageText = null; 58 59 private static String DUMMY_CONTACT_NAME = "__DUMMY CONTACT from runtime permissions sample"; 60 61 /** 62 * Projection for the content provider query includes the id and primary name of a contact. 63 */ 64 private static final String[] PROJECTION = {ContactsContract.Contacts._ID, 65 ContactsContract.Contacts.DISPLAY_NAME_PRIMARY}; 66 /** 67 * Sort order for the query. Sorted by primary name in ascending order. 68 */ 69 private static final String ORDER = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " ASC"; 70 71 72 /** 73 * Creates a new instance of a ContactsFragment. 74 */ newInstance()75 public static ContactsFragment newInstance() { 76 return new ContactsFragment(); 77 } 78 79 80 @Nullable 81 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)82 public View onCreateView(LayoutInflater inflater, ViewGroup container, 83 Bundle savedInstanceState) { 84 View rootView = inflater.inflate(R.layout.fragment_contacts, container, false); 85 86 mMessageText = (TextView) rootView.findViewById(R.id.contact_message); 87 88 // Register a listener to add a dummy contact when a button is clicked. 89 Button button = (Button) rootView.findViewById(R.id.contact_add); 90 button.setOnClickListener(new View.OnClickListener() { 91 @Override 92 public void onClick(View view) { 93 insertDummyContact(); 94 } 95 }); 96 97 // Register a listener to display the first contact when a button is clicked. 98 button = (Button) rootView.findViewById(R.id.contact_load); 99 button.setOnClickListener(new View.OnClickListener() { 100 @Override 101 public void onClick(View view) { 102 loadContact(); 103 } 104 }); 105 return rootView; 106 } 107 108 /** 109 * Restart the Loader to query the Contacts content provider to display the first contact. 110 */ loadContact()111 private void loadContact() { 112 getLoaderManager().restartLoader(0, null, this); 113 } 114 115 /** 116 * Initialises a new {@link CursorLoader} that queries the {@link ContactsContract}. 117 */ 118 @Override onCreateLoader(int i, Bundle bundle)119 public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { 120 return new CursorLoader(getActivity(), ContactsContract.Contacts.CONTENT_URI, PROJECTION, 121 null, null, ORDER); 122 } 123 124 125 /** 126 * Dislays either the name of the first contact or a message. 127 */ 128 @Override onLoadFinished(Loader<Cursor> loader, Cursor cursor)129 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { 130 131 if (cursor != null) { 132 final int totalCount = cursor.getCount(); 133 if (totalCount > 0) { 134 cursor.moveToFirst(); 135 String name = cursor 136 .getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)); 137 mMessageText.setText( 138 getResources().getString(R.string.contacts_string, totalCount, name)); 139 Log.d(TAG, "First contact loaded: " + name); 140 Log.d(TAG, "Total number of contacts: " + totalCount); 141 Log.d(TAG, "Total number of contacts: " + totalCount); 142 } else { 143 Log.d(TAG, "List of contacts is empty."); 144 mMessageText.setText(R.string.contacts_empty); 145 } 146 } 147 } 148 149 @Override onLoaderReset(Loader<Cursor> loader)150 public void onLoaderReset(Loader<Cursor> loader) { 151 mMessageText.setText(R.string.contacts_empty); 152 } 153 154 /** 155 * Accesses the Contacts content provider directly to insert a new contact. 156 * <p> 157 * The contact is called "__DUMMY ENTRY" and only contains a name. 158 */ insertDummyContact()159 private void insertDummyContact() { 160 // Two operations are needed to insert a new contact. 161 ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(2); 162 163 // First, set up a new raw contact. 164 ContentProviderOperation.Builder op = 165 ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) 166 .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null) 167 .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null); 168 operations.add(op.build()); 169 170 // Next, set the name for the contact. 171 op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 172 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) 173 .withValue(ContactsContract.Data.MIMETYPE, 174 ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) 175 .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, 176 DUMMY_CONTACT_NAME); 177 operations.add(op.build()); 178 179 // Apply the operations. 180 ContentResolver resolver = getActivity().getContentResolver(); 181 try { 182 resolver.applyBatch(ContactsContract.AUTHORITY, operations); 183 } catch (RemoteException e) { 184 Log.d(TAG, "Could not add a new contact: " + e.getMessage()); 185 } catch (OperationApplicationException e) { 186 Log.d(TAG, "Could not add a new contact: " + e.getMessage()); 187 } 188 } 189 } 190