1 2 /* 3 * Copyright (C) 2011 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.browser; 19 20 import android.content.Context; 21 import android.content.SharedPreferences; 22 import android.content.SharedPreferences.Editor; 23 import android.database.Cursor; 24 import android.net.Uri; 25 import android.os.AsyncTask; 26 import android.os.Message; 27 import android.preference.PreferenceManager; 28 import android.provider.ContactsContract; 29 import android.util.Log; 30 import android.webkit.WebSettingsClassic.AutoFillProfile; 31 32 import java.util.concurrent.CountDownLatch; 33 34 public class AutofillHandler { 35 36 private AutoFillProfile mAutoFillProfile; 37 // Default to zero. In the case no profile is set up, the initial 38 // value will come from the AutoFillSettingsFragment when the user 39 // creates a profile. Otherwise, we'll read the ID of the last used 40 // profile from the prefs db. 41 private int mAutoFillActiveProfileId; 42 private static final int NO_AUTOFILL_PROFILE_SET = 0; 43 44 private CountDownLatch mLoaded = new CountDownLatch(1); 45 private Context mContext; 46 47 private static final String LOGTAG = "AutofillHandler"; 48 49 public AutofillHandler(Context context) { 50 mContext = context.getApplicationContext(); 51 } 52 53 /** 54 * Load settings from the browser app's database. It is performed in 55 * an AsyncTask as it involves plenty of slow disk IO. 56 * NOTE: Strings used for the preferences must match those specified 57 * in the various preference XML files. 58 */ 59 public void asyncLoadFromDb() { 60 // Run the initial settings load in an AsyncTask as it hits the 61 // disk multiple times through SharedPreferences and SQLite. We 62 // need to be certain though that this has completed before we start 63 // to load pages though, so in the worst case we will block waiting 64 // for it to finish in BrowserActivity.onCreate(). 65 new LoadFromDb().start(); 66 } 67 68 private void waitForLoad() { 69 try { 70 mLoaded.await(); 71 } catch (InterruptedException e) { 72 Log.w(LOGTAG, "Caught exception while waiting for AutofillProfile to load."); 73 } 74 } 75 76 private class LoadFromDb extends Thread { 77 78 @Override 79 public void run() { 80 // Note the lack of synchronization over mAutoFillActiveProfileId and 81 // mAutoFillProfile here. This is because we control all other access 82 // to these members through the public functions of this class, and they 83 // all wait for this thread via the mLoaded CountDownLatch. 84 85 SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(mContext); 86 87 // Read the last active AutoFill profile id. 88 mAutoFillActiveProfileId = p.getInt( 89 PreferenceKeys.PREF_AUTOFILL_ACTIVE_PROFILE_ID, 90 mAutoFillActiveProfileId); 91 92 // Load the autofill profile data from the database. We use a database separate 93 // to the browser preference DB to make it easier to support multiple profiles 94 // and switching between them. Note that this may block startup if this DB lookup 95 // is extremely slow. We do this to ensure that if there's a profile set, the 96 // user never sees the "setup Autofill" option. 97 AutoFillProfileDatabase autoFillDb = AutoFillProfileDatabase.getInstance(mContext); 98 Cursor c = autoFillDb.getProfile(mAutoFillActiveProfileId); 99 100 if (c.getCount() > 0) { 101 c.moveToFirst(); 102 103 String fullName = c.getString(c.getColumnIndex( 104 AutoFillProfileDatabase.Profiles.FULL_NAME)); 105 String email = c.getString(c.getColumnIndex( 106 AutoFillProfileDatabase.Profiles.EMAIL_ADDRESS)); 107 String company = c.getString(c.getColumnIndex( 108 AutoFillProfileDatabase.Profiles.COMPANY_NAME)); 109 String addressLine1 = c.getString(c.getColumnIndex( 110 AutoFillProfileDatabase.Profiles.ADDRESS_LINE_1)); 111 String addressLine2 = c.getString(c.getColumnIndex( 112 AutoFillProfileDatabase.Profiles.ADDRESS_LINE_2)); 113 String city = c.getString(c.getColumnIndex( 114 AutoFillProfileDatabase.Profiles.CITY)); 115 String state = c.getString(c.getColumnIndex( 116 AutoFillProfileDatabase.Profiles.STATE)); 117 String zip = c.getString(c.getColumnIndex( 118 AutoFillProfileDatabase.Profiles.ZIP_CODE)); 119 String country = c.getString(c.getColumnIndex( 120 AutoFillProfileDatabase.Profiles.COUNTRY)); 121 String phone = c.getString(c.getColumnIndex( 122 AutoFillProfileDatabase.Profiles.PHONE_NUMBER)); 123 mAutoFillProfile = new AutoFillProfile(mAutoFillActiveProfileId, 124 fullName, email, company, addressLine1, addressLine2, city, 125 state, zip, country, phone); 126 } 127 c.close(); 128 autoFillDb.close(); 129 130 // At this point we've loaded the profile if there was one, so let any thread 131 // waiting on initialization continue. 132 mLoaded.countDown(); 133 134 // Synchronization note: strictly speaking, it's possible that mAutoFillProfile 135 // may get a value after we check below, but that's OK. This check is only an 136 // optimisation, and we do a proper synchronized check further down when it comes 137 // to actually setting the inferred profile. 138 if (mAutoFillProfile == null) { 139 // We did not load a profile from disk. Try to infer one from the user's 140 // "me" contact. 141 final Uri profileUri = Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI, 142 ContactsContract.Contacts.Data.CONTENT_DIRECTORY); 143 String name = getContactField(profileUri, 144 ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, 145 ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); 146 // Only attempt to read other data and set a profile if we could successfully 147 // get a name. 148 if (name != null) { 149 String email = getContactField(profileUri, 150 ContactsContract.CommonDataKinds.Email.ADDRESS, 151 ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE); 152 String phone = getContactField(profileUri, 153 ContactsContract.CommonDataKinds.Phone.NUMBER, 154 ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE); 155 String company = getContactField(profileUri, 156 ContactsContract.CommonDataKinds.Organization.COMPANY, 157 ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE); 158 159 // Can't easily get structured postal address information (even using 160 // CommonDataKinds.StructuredPostal) so omit prepopulating that for now. 161 // When querying structured postal data, it often all comes back as a string 162 // inside the "street" field. 163 164 synchronized(AutofillHandler.this) { 165 // Only use this profile if one hasn't been set inbetween the 166 // inital import and this thread getting to this point. 167 if (mAutoFillProfile == null) { 168 setAutoFillProfile(new AutoFillProfile(1, name, email, company, 169 null, null, null, null, null, null, phone), null); 170 } 171 } 172 } 173 } 174 } 175 176 private String getContactField(Uri uri, String field, String itemType) { 177 String result = null; 178 179 Cursor c = mContext.getContentResolver().query(uri, new String[] { field }, 180 ContactsContract.Data.MIMETYPE + "=?", new String[] { itemType }, null); 181 182 if (c == null) { 183 return null; 184 } 185 186 try { 187 // Just use the first returned value if we get more than one. 188 if (c.moveToFirst()) { 189 result = c.getString(0); 190 } 191 } finally { 192 c.close(); 193 } 194 return result; 195 } 196 } 197 198 public synchronized void setAutoFillProfile(AutoFillProfile profile, Message msg) { 199 waitForLoad(); 200 int profileId = NO_AUTOFILL_PROFILE_SET; 201 if (profile != null) { 202 profileId = profile.getUniqueId(); 203 // Update the AutoFill DB with the new profile. 204 new SaveProfileToDbTask(msg).execute(profile); 205 } else { 206 // Delete the current profile. 207 if (mAutoFillProfile != null) { 208 new DeleteProfileFromDbTask(msg).execute(mAutoFillProfile.getUniqueId()); 209 } 210 } 211 // Make sure we set mAutoFillProfile before calling setActiveAutoFillProfileId 212 // Calling setActiveAutoFillProfileId will trigger an update of WebViews 213 // which will expect a new profile to be set 214 mAutoFillProfile = profile; 215 setActiveAutoFillProfileId(profileId); 216 } 217 218 public synchronized AutoFillProfile getAutoFillProfile() { 219 waitForLoad(); 220 return mAutoFillProfile; 221 } 222 223 private synchronized void setActiveAutoFillProfileId(int activeProfileId) { 224 mAutoFillActiveProfileId = activeProfileId; 225 Editor ed = PreferenceManager. 226 getDefaultSharedPreferences(mContext).edit(); 227 ed.putInt(PreferenceKeys.PREF_AUTOFILL_ACTIVE_PROFILE_ID, activeProfileId); 228 ed.apply(); 229 } 230 231 private abstract class AutoFillProfileDbTask<T> extends AsyncTask<T, Void, Void> { 232 AutoFillProfileDatabase mAutoFillProfileDb; 233 Message mCompleteMessage; 234 235 public AutoFillProfileDbTask(Message msg) { 236 mCompleteMessage = msg; 237 } 238 239 @Override 240 protected void onPostExecute(Void result) { 241 if (mCompleteMessage != null) { 242 mCompleteMessage.sendToTarget(); 243 } 244 mAutoFillProfileDb.close(); 245 } 246 247 @Override 248 abstract protected Void doInBackground(T... values); 249 } 250 251 252 private class SaveProfileToDbTask extends AutoFillProfileDbTask<AutoFillProfile> { 253 public SaveProfileToDbTask(Message msg) { 254 super(msg); 255 } 256 257 @Override 258 protected Void doInBackground(AutoFillProfile... values) { 259 mAutoFillProfileDb = AutoFillProfileDatabase.getInstance(mContext); 260 synchronized (AutofillHandler.this) { 261 assert mAutoFillActiveProfileId != NO_AUTOFILL_PROFILE_SET; 262 AutoFillProfile newProfile = values[0]; 263 mAutoFillProfileDb.addOrUpdateProfile(mAutoFillActiveProfileId, newProfile); 264 } 265 return null; 266 } 267 } 268 269 private class DeleteProfileFromDbTask extends AutoFillProfileDbTask<Integer> { 270 public DeleteProfileFromDbTask(Message msg) { 271 super(msg); 272 } 273 274 @Override 275 protected Void doInBackground(Integer... values) { 276 mAutoFillProfileDb = AutoFillProfileDatabase.getInstance(mContext); 277 int id = values[0]; 278 assert id > 0; 279 mAutoFillProfileDb.dropProfile(id); 280 return null; 281 } 282 } 283 } 284