Home | History | Annotate | Download | only in browser
      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