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