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 
PackageEventArg(String packageName, PendingResult broadcastPendingResult)57         private PackageEventArg(String packageName, PendingResult broadcastPendingResult) {
58             this.packageName = packageName;
59             this.broadcastPendingResult = broadcastPendingResult;
60         }
61     }
62 
ContactsPackageMonitor(Context context)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 
start()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 
start(Context context)88     public static synchronized void start(Context context) {
89         if (sInstance == null) {
90             sInstance = new ContactsPackageMonitor(context);
91             sInstance.start();
92         }
93     }
94 
registerReceiver()95     private void registerReceiver() {
96         final IntentFilter filter = new IntentFilter();
97 
98         filter.addAction(Intent.ACTION_PACKAGE_UNSTOPPED);
99         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
100         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
101         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
102         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
103         filter.addDataScheme("package");
104 
105         mContext.registerReceiver(new BroadcastReceiver() {
106             @Override
107             public void onReceive(Context context, Intent intent) {
108                 if (intent.getData() == null) {
109                     return; // Shouldn't happen.
110                 }
111                 final String changedPackage = intent.getData().getSchemeSpecificPart();
112                 final PendingResult result = goAsync();
113 
114                 mTaskScheduler.scheduleTask(BACKGROUND_TASK_PACKAGE_EVENT,
115                         new PackageEventArg(changedPackage, result));
116             }
117         }, filter);
118     }
119 
onPackageChanged(PackageEventArg arg)120     private void onPackageChanged(PackageEventArg arg) {
121         try {
122             final String packageName = arg.packageName;
123             if (TextUtils.isEmpty(packageName)) {
124                 Log.w(TAG, "Empty package name detected.");
125                 return;
126             }
127             if (VERBOSE_LOGGING) Log.d(TAG, "onPackageChanged: Scanning package: " + packageName);
128 
129             // First, tell CP2.
130             final ContactsProvider2 provider = getProvider(mContext, ContactsContract.AUTHORITY);
131             if (provider != null) {
132                 provider.onPackageChanged(packageName);
133             }
134 
135             // Next, if the package is gone, clean up the voicemail.
136             cleanupVoicemail(mContext, packageName);
137         } finally {
138             if (VERBOSE_LOGGING) Log.v(TAG, "Calling PendingResult.finish()...");
139             arg.broadcastPendingResult.finish();
140         }
141     }
142 
143     @VisibleForTesting
cleanupVoicemail(Context context, String packageName)144     static void cleanupVoicemail(Context context, String packageName) {
145         if (PackageUtils.isPackageInstalled(context, packageName)) {
146             return; // Still installed.
147         }
148         if (VERBOSE_LOGGING) Log.d(TAG, "Cleaning up data for package: " + packageName);
149 
150         // Delete both voicemail content and voicemail status entries for this package.
151         final VoicemailContentProvider provider = getProvider(context, VoicemailContract.AUTHORITY);
152         if (provider != null) {
153             provider.removeBySourcePackage(packageName);
154         }
155     }
156 
getProvider(Context context, String authority)157     private static <T extends ContentProvider> T getProvider(Context context, String authority) {
158         final IContentProvider iprovider = context.getContentResolver().acquireProvider(authority);
159         final ContentProvider provider = ContentProvider.coerceToLocalContentProvider(iprovider);
160         if (provider != null) {
161             return (T) provider;
162         }
163         Slog.wtf(TAG, "Provider for " + authority + " not found");
164         return null;
165     }
166 }
167