Home | History | Annotate | Download | only in map
      1 /*
      2 * Copyright (C) 2014 Samsung System LSI
      3 * Licensed under the Apache License, Version 2.0 (the "License");
      4 * you may not use this file except in compliance with the License.
      5 * You may obtain a copy of the License at
      6 *
      7 *      http://www.apache.org/licenses/LICENSE-2.0
      8 *
      9 * Unless required by applicable law or agreed to in writing, software
     10 * distributed under the License is distributed on an "AS IS" BASIS,
     11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 * See the License for the specific language governing permissions and
     13 * limitations under the License.
     14 */
     15 package com.android.bluetooth.map;
     16 
     17 import java.util.ArrayList;
     18 import java.util.LinkedHashMap;
     19 import java.util.List;
     20 
     21 import android.content.BroadcastReceiver;
     22 import android.content.ContentResolver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.content.pm.PackageInfo;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.PackageManager.NameNotFoundException;
     29 import android.content.pm.ResolveInfo;
     30 import android.database.ContentObserver;
     31 import android.net.Uri;
     32 import android.os.Handler;
     33 import com.android.bluetooth.mapapi.BluetoothMapContract;
     34 import android.util.Log;
     35 
     36 /**
     37  * Class to construct content observers for for email applications on the system.
     38  *
     39  *
     40  */
     41 
     42 public class BluetoothMapAppObserver{
     43 
     44     private static final String TAG = "BluetoothMapAppObserver";
     45 
     46     private static final boolean D = BluetoothMapService.DEBUG;
     47     private static final boolean V = BluetoothMapService.VERBOSE;
     48     /*  */
     49     private LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> mFullList;
     50     private LinkedHashMap<String,ContentObserver> mObserverMap =
     51             new LinkedHashMap<String,ContentObserver>();
     52     private ContentResolver mResolver;
     53     private Context mContext;
     54     private BroadcastReceiver mReceiver;
     55     private PackageManager mPackageManager = null;
     56     BluetoothMapAccountLoader mLoader;
     57     BluetoothMapService mMapService = null;
     58     private boolean mRegisteredReceiver = false;
     59 
     60     public BluetoothMapAppObserver(final Context context, BluetoothMapService mapService) {
     61         mContext    = context;
     62         mMapService = mapService;
     63         mResolver   = context.getContentResolver();
     64         mLoader     = new BluetoothMapAccountLoader(mContext);
     65         mFullList   = mLoader.parsePackages(false); /* Get the current list of apps */
     66         createReceiver();
     67         initObservers();
     68     }
     69 
     70 
     71     private BluetoothMapAccountItem getApp(String authoritiesName) {
     72         if(V) Log.d(TAG, "getApp(): Looking for " + authoritiesName);
     73         for(BluetoothMapAccountItem app:mFullList.keySet()){
     74             if(V) Log.d(TAG, "  Comparing: " + app.getProviderAuthority());
     75             if(app.getProviderAuthority().equals(authoritiesName)) {
     76                 if(V) Log.d(TAG, "  found " + app.mBase_uri_no_account);
     77                 return app;
     78             }
     79         }
     80         if(V) Log.d(TAG, "  NOT FOUND!");
     81         return null;
     82     }
     83 
     84     private void handleAccountChanges(String packageNameWithProvider) {
     85 
     86         if(D)Log.d(TAG,"handleAccountChanges (packageNameWithProvider: "
     87                         +packageNameWithProvider+"\n");
     88         //String packageName = packageNameWithProvider.replaceFirst("\\.[^\\.]+$", "");
     89         BluetoothMapAccountItem app = getApp(packageNameWithProvider);
     90         if(app != null) {
     91             ArrayList<BluetoothMapAccountItem> newAccountList = mLoader.parseAccounts(app);
     92             ArrayList<BluetoothMapAccountItem> oldAccountList = mFullList.get(app);
     93             ArrayList<BluetoothMapAccountItem> addedAccountList =
     94                     (ArrayList<BluetoothMapAccountItem>)newAccountList.clone();
     95             // Same as oldAccountList.clone
     96             ArrayList<BluetoothMapAccountItem> removedAccountList = mFullList.get(app);
     97             if (oldAccountList == null)
     98                 oldAccountList = new ArrayList <BluetoothMapAccountItem>();
     99             if (removedAccountList == null)
    100                 removedAccountList = new ArrayList <BluetoothMapAccountItem>();
    101 
    102             mFullList.put(app, newAccountList);
    103             for(BluetoothMapAccountItem newAcc: newAccountList){
    104                 for(BluetoothMapAccountItem oldAcc: oldAccountList){
    105                     if(newAcc.getId() == oldAcc.getId()){
    106                         // For each match remove from both removed and added lists
    107                         removedAccountList.remove(oldAcc);
    108                         addedAccountList.remove(newAcc);
    109                         if(!newAcc.getName().equals(oldAcc.getName()) && newAcc.mIsChecked){
    110                             // Name Changed and the acc is visible - Change Name in SDP record
    111                             mMapService.updateMasInstances(
    112                                     BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED);
    113                             if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED");
    114                         }
    115                         if(newAcc.mIsChecked != oldAcc.mIsChecked) {
    116                             // Visibility changed
    117                             if(newAcc.mIsChecked){
    118                                 // account added - create SDP record
    119                                 mMapService.updateMasInstances(
    120                                         BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
    121                                 if(V)Log.d(TAG, "UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " +
    122                                         "isChecked changed");
    123                             } else {
    124                                 // account removed - remove SDP record
    125                                 mMapService.updateMasInstances(
    126                                         BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
    127                                 if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " +
    128                                         "isChecked changed");
    129                             }
    130                         }
    131                         break;
    132                     }
    133                 }
    134             }
    135             // Notify on any removed accounts
    136             for(BluetoothMapAccountItem removedAcc: removedAccountList){
    137                 mMapService.updateMasInstances(
    138                         BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
    139                 if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " + removedAcc);
    140             }
    141             // Notify on any new accounts
    142             for(BluetoothMapAccountItem addedAcc: addedAccountList){
    143                 mMapService.updateMasInstances(
    144                         BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
    145                 if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " + addedAcc);
    146             }
    147 
    148         } else {
    149             Log.e(TAG, "Received change notification on package not registered for notifications!");
    150 
    151         }
    152     }
    153 
    154     /**
    155      * Adds a new content observer to the list of content observers.
    156      * The key for the observer is the uri as string
    157      * @param uri uri for the package that supports MAP email
    158      */
    159 
    160     public void registerObserver(BluetoothMapAccountItem app) {
    161         Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
    162         if (V) Log.d(TAG, "registerObserver for URI "+uri.toString()+"\n");
    163         ContentObserver observer = new ContentObserver(null) {
    164             @Override
    165             public void onChange(boolean selfChange) {
    166                 onChange(selfChange, null);
    167             }
    168 
    169             @Override
    170             public void onChange(boolean selfChange, Uri uri) {
    171                 if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
    172                         + " Uri: " + uri + " selfchange: " + selfChange);
    173                 if(uri != null) {
    174                     handleAccountChanges(uri.getHost());
    175                 } else {
    176                     Log.e(TAG, "Unable to handle change as the URI is NULL!");
    177                 }
    178 
    179             }
    180         };
    181         mObserverMap.put(uri.toString(), observer);
    182         //False "notifyForDescendents" : Get notified whenever a change occurs to the exact URI.
    183         mResolver.registerContentObserver(uri, false, observer);
    184     }
    185 
    186     public void unregisterObserver(BluetoothMapAccountItem app) {
    187         Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
    188         if (V) Log.d(TAG, "unregisterObserver("+uri.toString()+")\n");
    189         mResolver.unregisterContentObserver(mObserverMap.get(uri.toString()));
    190         mObserverMap.remove(uri.toString());
    191     }
    192 
    193     private void initObservers(){
    194         if(D)Log.d(TAG,"initObservers()");
    195         for(BluetoothMapAccountItem app: mFullList.keySet()){
    196             registerObserver(app);
    197         }
    198     }
    199 
    200     private void deinitObservers(){
    201         if(D)Log.d(TAG,"deinitObservers()");
    202         for(BluetoothMapAccountItem app: mFullList.keySet()){
    203             unregisterObserver(app);
    204         }
    205     }
    206 
    207     private void createReceiver(){
    208         if(D)Log.d(TAG,"createReceiver()\n");
    209         IntentFilter intentFilter = new IntentFilter();
    210         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
    211         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
    212         intentFilter.addDataScheme("package");
    213         mReceiver = new BroadcastReceiver() {
    214             @Override
    215             public void onReceive(Context context, Intent intent) {
    216                 if(D)Log.d(TAG,"onReceive\n");
    217                 String action = intent.getAction();
    218 
    219                 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
    220                     Uri data = intent.getData();
    221                     String packageName = data.getEncodedSchemeSpecificPart();
    222                     if(D)Log.d(TAG,"The installed package is: "+ packageName);
    223 
    224                     BluetoothMapUtils.TYPE msgType = BluetoothMapUtils.TYPE.NONE;
    225                     ResolveInfo resolveInfo = null;
    226                     Intent[] searchIntents = new Intent[2];
    227                     //Array <Intent> searchIntents = new Array <Intent>();
    228                     searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL);
    229                     searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM);
    230                     // Find all installed packages and filter out those that support Bluetooth Map.
    231 
    232                     mPackageManager = mContext.getPackageManager();
    233 
    234                     for (Intent searchIntent : searchIntents) {
    235                         List<ResolveInfo> resInfos =
    236                                 mPackageManager.queryIntentContentProviders(searchIntent, 0);
    237                         if (resInfos != null ) {
    238                             if(D) Log.d(TAG,"Found " + resInfos.size()
    239                                     + " application(s) with intent "
    240                                     + searchIntent.getAction().toString());
    241                             for (ResolveInfo rInfo : resInfos) {
    242                                 if(rInfo != null) {
    243                                     // Find out if package contain Bluetooth MAP support
    244                                     if (packageName.equals(rInfo.providerInfo.packageName)) {
    245                                         resolveInfo = rInfo;
    246                                         if(searchIntent.getAction() ==
    247                                                 BluetoothMapContract.PROVIDER_INTERFACE_EMAIL){
    248                                             msgType = BluetoothMapUtils.TYPE.EMAIL;
    249                                         } else if (searchIntent.getAction() ==
    250                                                 BluetoothMapContract.PROVIDER_INTERFACE_IM){
    251                                             msgType = BluetoothMapUtils.TYPE.IM;
    252                                         }
    253                                         break;
    254                                     }
    255                                 }
    256                             }
    257                         }
    258                     }
    259                     // if application found with Bluetooth MAP support add to list
    260                     if(resolveInfo != null) {
    261                         if(D) Log.d(TAG,"Found " + resolveInfo.providerInfo.packageName
    262                                 + " application of type " + msgType);
    263                         BluetoothMapAccountItem app = mLoader.createAppItem(resolveInfo,
    264                                 false, msgType);
    265                         if(app != null) {
    266                             registerObserver(app);
    267                             // Add all accounts to mFullList
    268                             ArrayList<BluetoothMapAccountItem> newAccountList =
    269                                     mLoader.parseAccounts(app);
    270                             mFullList.put(app, newAccountList);
    271                         }
    272                     }
    273 
    274                 }
    275                 else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
    276                     Uri data = intent.getData();
    277                     String packageName = data.getEncodedSchemeSpecificPart();
    278                     if(D)Log.d(TAG,"The removed package is: "+ packageName);
    279                     BluetoothMapAccountItem app = getApp(packageName);
    280                     /* Find the object and remove from fullList */
    281                     if(app != null) {
    282                         unregisterObserver(app);
    283                         mFullList.remove(app);
    284                     }
    285                 }
    286             }
    287         };
    288         if (!mRegisteredReceiver) {
    289             try {
    290                 mContext.registerReceiver(mReceiver,intentFilter);
    291                 mRegisteredReceiver = true;
    292             } catch (Exception e) {
    293                 Log.e(TAG,"Unable to register MapAppObserver receiver", e);
    294             }
    295         }
    296     }
    297 
    298     private void removeReceiver(){
    299         if(D)Log.d(TAG,"removeReceiver()\n");
    300         if (mRegisteredReceiver) {
    301             try {
    302                 mRegisteredReceiver = false;
    303                 mContext.unregisterReceiver(mReceiver);
    304             } catch (Exception e) {
    305                 Log.e(TAG,"Unable to unregister mapAppObserver receiver", e);
    306             }
    307         }
    308     }
    309 
    310     /**
    311      * Method to get a list of the accounts (across all apps) that are set to be shared
    312      * through MAP.
    313      * @return Arraylist<BluetoothMapAccountItem> containing all enabled accounts
    314      */
    315     public ArrayList<BluetoothMapAccountItem> getEnabledAccountItems(){
    316         if(D)Log.d(TAG,"getEnabledAccountItems()\n");
    317         ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
    318         for (BluetoothMapAccountItem app:mFullList.keySet()){
    319             if (app != null) {
    320                 ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
    321                 if (accountList != null) {
    322                     for (BluetoothMapAccountItem acc: accountList) {
    323                         if (acc.mIsChecked) {
    324                             list.add(acc);
    325                         }
    326                     }
    327                 } else {
    328                     Log.w(TAG,"getEnabledAccountItems() - No AccountList enabled\n");
    329                 }
    330             } else {
    331                 Log.w(TAG,"getEnabledAccountItems() - No Account in App enabled\n");
    332             }
    333         }
    334         return list;
    335     }
    336 
    337     /**
    338      * Method to get a list of the accounts (across all apps).
    339      * @return Arraylist<BluetoothMapAccountItem> containing all accounts
    340      */
    341     public ArrayList<BluetoothMapAccountItem> getAllAccountItems(){
    342         if(D)Log.d(TAG,"getAllAccountItems()\n");
    343         ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
    344         for(BluetoothMapAccountItem app:mFullList.keySet()){
    345             ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
    346             list.addAll(accountList);
    347         }
    348         return list;
    349     }
    350 
    351 
    352     /**
    353      * Cleanup all resources - must be called to avoid leaks.
    354      */
    355     public void shutdown() {
    356         deinitObservers();
    357         removeReceiver();
    358     }
    359 }
    360