Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2017 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 package com.android.providers.contacts;
     17 
     18 import android.content.BroadcastReceiver;
     19 import android.content.BroadcastReceiver.PendingResult;
     20 import android.content.ContentProvider;
     21 import android.content.Context;
     22 import android.content.IContentProvider;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.provider.ContactsContract;
     26 import android.provider.VoicemailContract;
     27 import android.text.TextUtils;
     28 import android.util.Log;
     29 import android.util.Slog;
     30 
     31 import com.android.providers.contacts.util.PackageUtils;
     32 
     33 import com.google.common.annotations.VisibleForTesting;
     34 
     35 /**
     36  * - Handles package related broadcasts.
     37  * - Also scan changed packages while the process wasn't running using PM.getChangedPackages().
     38  */
     39 public class ContactsPackageMonitor {
     40     private static final String TAG = "ContactsPackageMonitor";
     41 
     42     private static final boolean VERBOSE_LOGGING = AbstractContactsProvider.VERBOSE_LOGGING;
     43 
     44     private static final int BACKGROUND_TASK_PACKAGE_EVENT = 0;
     45 
     46     private static ContactsPackageMonitor sInstance;
     47 
     48     private Context mContext;
     49 
     50     /** We run all BG tasks on this thread/handler sequentially. */
     51     private final ContactsTaskScheduler mTaskScheduler;
     52 
     53     private static class PackageEventArg {
     54         final String packageName;
     55         final PendingResult broadcastPendingResult;
     56 
     57         private PackageEventArg(String packageName, PendingResult broadcastPendingResult) {
     58             this.packageName = packageName;
     59             this.broadcastPendingResult = broadcastPendingResult;
     60         }
     61     }
     62 
     63     private ContactsPackageMonitor(Context context) {
     64         mContext = context; // Can't use the app context due to a bug with shared process.
     65 
     66         // Start the BG thread and register the receiver.
     67         mTaskScheduler = new ContactsTaskScheduler(getClass().getSimpleName()) {
     68             @Override
     69             public void onPerformTask(int taskId, Object arg) {
     70                 switch (taskId) {
     71                     case BACKGROUND_TASK_PACKAGE_EVENT:
     72                         onPackageChanged((PackageEventArg) arg);
     73                         break;
     74                 }
     75             }
     76         };
     77     }
     78 
     79     private void start() {
     80         if (VERBOSE_LOGGING) {
     81             Log.v(TAG, "Starting... user="
     82                     + android.os.Process.myUserHandle().getIdentifier());
     83         }
     84 
     85         registerReceiver();
     86     }
     87 
     88     public static synchronized void start(Context context) {
     89         if (sInstance == null) {
     90             sInstance = new ContactsPackageMonitor(context);
     91             sInstance.start();
     92         }
     93     }
     94 
     95     private void registerReceiver() {
     96         final IntentFilter filter = new IntentFilter();
     97 
     98         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
     99         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
    100         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
    101         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
    102         filter.addDataScheme("package");
    103 
    104         mContext.registerReceiver(new BroadcastReceiver() {
    105             @Override
    106             public void onReceive(Context context, Intent intent) {
    107                 if (intent.getData() == null) {
    108                     return; // Shouldn't happen.
    109                 }
    110                 final String changedPackage = intent.getData().getSchemeSpecificPart();
    111                 final PendingResult result = goAsync();
    112 
    113                 mTaskScheduler.scheduleTask(BACKGROUND_TASK_PACKAGE_EVENT,
    114                         new PackageEventArg(changedPackage, result));
    115             }
    116         }, filter);
    117     }
    118 
    119     private void onPackageChanged(PackageEventArg arg) {
    120         try {
    121             final String packageName = arg.packageName;
    122             if (TextUtils.isEmpty(packageName)) {
    123                 Log.w(TAG, "Empty package name detected.");
    124                 return;
    125             }
    126             if (VERBOSE_LOGGING) Log.d(TAG, "onPackageChanged: Scanning package: " + packageName);
    127 
    128             // First, tell CP2.
    129             final ContactsProvider2 provider = getProvider(mContext, ContactsContract.AUTHORITY);
    130             if (provider != null) {
    131                 provider.onPackageChanged(packageName);
    132             }
    133 
    134             // Next, if the package is gone, clean up the voicemail.
    135             cleanupVoicemail(mContext, packageName);
    136         } finally {
    137             if (VERBOSE_LOGGING) Log.v(TAG, "Calling PendingResult.finish()...");
    138             arg.broadcastPendingResult.finish();
    139         }
    140     }
    141 
    142     @VisibleForTesting
    143     static void cleanupVoicemail(Context context, String packageName) {
    144         if (PackageUtils.isPackageInstalled(context, packageName)) {
    145             return; // Still installed.
    146         }
    147         if (VERBOSE_LOGGING) Log.d(TAG, "Cleaning up data for package: " + packageName);
    148 
    149         // Delete both voicemail content and voicemail status entries for this package.
    150         final VoicemailContentProvider provider = getProvider(context, VoicemailContract.AUTHORITY);
    151         if (provider != null) {
    152             provider.removeBySourcePackage(packageName);
    153         }
    154     }
    155 
    156     private static <T extends ContentProvider> T getProvider(Context context, String authority) {
    157         final IContentProvider iprovider = context.getContentResolver().acquireProvider(authority);
    158         final ContentProvider provider = ContentProvider.coerceToLocalContentProvider(iprovider);
    159         if (provider != null) {
    160             return (T) provider;
    161         }
    162         Slog.wtf(TAG, "Provider for " + authority + " not found");
    163         return null;
    164     }
    165 }
    166