1 /*
2  * Copyright (C) 2011 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 
17 package com.android.musicfx;
18 
19 import android.app.Activity;
20 import android.app.IntentService;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.SharedPreferences;
26 import android.content.SharedPreferences.Editor;
27 import android.content.pm.PackageManager;
28 import android.content.pm.ResolveInfo;
29 import android.media.audiofx.AudioEffect;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.util.Log;
33 
34 import java.util.List;
35 
36 /**
37  * Provide backwards compatibility for existing control panels.
38  * There are two major parts to this:
39  * - a BroadcastReceiver that listens for installed or removed packages, and
40  *   enables or disables control panel receivers as needed to ensure that only
41  *   one control panel package will receive the broadcasts that applications end
42  * - a high priority control panel activity that redirects to the currently
43  *   selected control panel activity
44  *
45  */
46 public class Compatibility {
47 
48     private final static String TAG = "MusicFXCompat";
49     // run "setprop log.tag.MusicFXCompat DEBUG" to turn on logging
50     private final static boolean LOG = Log.isLoggable(TAG, Log.DEBUG);
51 
52 
53     /**
54      * This activity has an intent filter with the highest possible priority, so
55      * it will always be chosen. It then looks up the correct control panel to
56      * use and launches that.
57      */
58     public static class Redirector extends Activity {
59 
60         @Override
onCreate(final Bundle savedInstanceState)61         public void onCreate(final Bundle savedInstanceState) {
62             super.onCreate(savedInstanceState);
63             log("Compatibility Activity called from " + getCallingPackage());
64             Intent i = new Intent(getIntent());
65             i.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
66             SharedPreferences pref = getSharedPreferences("musicfx", MODE_PRIVATE);
67             String defPackage = pref.getString("defaultpanelpackage", null);
68             String defName = pref.getString("defaultpanelname", null);
69             log("read " + defPackage + "/" + defName + " as default");
70             if (defPackage == null || defName == null) {
71                 Log.e(TAG, "no default set!");
72                 ResolveInfo defPanel = Service.searchControlPanel(this, null);
73                 if (defPanel == null) {
74                     finish();
75                     return;
76                 }
77                 defPackage = defPanel.activityInfo.packageName;
78                 defName = defPanel.activityInfo.name;
79                 // use the built-in panel
80                 i.setComponent(new ComponentName(defPackage, defName));
81                 // also save it as the default
82                 Intent updateIntent = new Intent(this, Service.class);
83                 updateIntent.putExtra("defPackage", defPackage);
84                 updateIntent.putExtra("defName", defName);
85                 startService(updateIntent);
86             } else {
87                 i.setComponent(new ComponentName(defPackage, defName));
88             }
89             startActivity(i);
90             finish();
91         }
92     }
93 
94     /**
95      * This BroadcastReceiver responds to BOOT_COMPLETED, PACKAGE_ADDED,
96      * PACKAGE_REPLACED and PACKAGE_REMOVED intents. When run, it checks
97      * to see whether the active control panel needs to be updated:
98      * - if there is no default, it picks one
99      * - if a new control panel is installed, it becomes the default
100      * It then enables the open/close receivers in the active control panel,
101      * and disables them in the others.
102      */
103     public static class Receiver extends BroadcastReceiver {
104 
105         @Override
onReceive(final Context context, final Intent intent)106         public void onReceive(final Context context, final Intent intent) {
107 
108             log("received");
109             Intent updateIntent = new Intent(context, Service.class);
110             updateIntent.putExtra("reason", intent);
111             context.startService(updateIntent);
112         }
113     }
114 
115     public static class Service extends IntentService {
116 
117         PackageManager mPackageManager;
118 
Service()119         public Service() {
120             super("CompatibilityService");
121         }
122 
123         @Override
onHandleIntent(final Intent intent)124         protected void onHandleIntent(final Intent intent) {
125             log("handleintent");
126             if (mPackageManager == null) {
127                 mPackageManager = getPackageManager();
128             }
129 
130             String defPackage = intent.getStringExtra("defPackage");
131             String defName = intent.getStringExtra("defName");
132             if (defPackage != null && defName != null) {
133                 setDefault(defPackage, defName);
134                 return;
135             }
136 
137             Intent packageIntent = intent.getParcelableExtra("reason");
138             Bundle b = packageIntent.getExtras();
139             if (b != null) b.size();
140             log("intentservice saw: " + packageIntent + " " + b);
141             // TODO, be smarter about package upgrades (which results in three
142             // broadcasts: removed, added, replaced)
143             Uri packageUri = packageIntent.getData();
144             String updatedPackage = null;
145             if (packageUri != null) {
146                 updatedPackage = packageUri.toString().substring(8);
147                 pickDefaultControlPanel(updatedPackage);
148             }
149         }
150 
searchControlPanel(Context context, String updatedPackage)151         private static final ResolveInfo searchControlPanel(Context context, String updatedPackage) {
152 
153             ResolveInfo defPanel = null;
154             ResolveInfo otherPanel = null;
155             ResolveInfo thisPanel = null;
156             Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
157             List<ResolveInfo> ris = context.getPackageManager().queryIntentActivities(
158                                     i, PackageManager.GET_DISABLED_COMPONENTS);
159             log("found: " + ris.size());
160             SharedPreferences pref = context.getSharedPreferences("musicfx", Context.MODE_PRIVATE);
161             String savedDefPackage = pref.getString("defaultpanelpackage", null);
162             String savedDefName = pref.getString("defaultpanelname", null);
163             log("saved default: " + savedDefName);
164             for (ResolveInfo foo: ris) {
165                 if (foo.activityInfo.name.equals(Compatibility.Redirector.class.getName())) {
166                     log("skipping " + foo);
167                     continue;
168                 }
169                 log("considering " + foo);
170                 if (foo.activityInfo.name.equals(savedDefName) &&
171                         foo.activityInfo.packageName.equals(savedDefPackage) &&
172                         foo.activityInfo.enabled) {
173                     log("default: " + savedDefName);
174                     defPanel = foo;
175                     break;
176                 } else if (foo.activityInfo.packageName.equals(updatedPackage)) {
177                     log("choosing newly installed package " + updatedPackage);
178                     otherPanel = foo;
179                 } else if (otherPanel == null &&
180                         !foo.activityInfo.packageName.equals(context.getPackageName())) {
181                     otherPanel = foo;
182                 } else {
183                     thisPanel = foo;
184                 }
185             }
186 
187             if (defPanel == null) {
188                 // pick a default control panel
189                 if (otherPanel == null) {
190                     if (thisPanel == null) {
191                         Log.e(TAG, "No control panels found!");
192                         return null;
193                     }
194                     otherPanel = thisPanel;
195                 }
196                 defPanel = otherPanel;
197             }
198             return defPanel;
199         }
200 
pickDefaultControlPanel(String updatedPackage)201         private void pickDefaultControlPanel(String updatedPackage) {
202             ResolveInfo defPanel = searchControlPanel(this, updatedPackage);
203             if (defPanel == null) {
204                 return;
205             }
206             // Now that we have selected a default control panel activity, ensure
207             // that the broadcast receiver(s) in that same package are enabled,
208             // and the ones in the other packages are disabled.
209             String defPackage = defPanel.activityInfo.packageName;
210             String defName = defPanel.activityInfo.name;
211             setDefault(defPackage, defName);
212         }
213 
setDefault(String defPackage, String defName)214         private void setDefault(String defPackage, String defName) {
215             Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
216             List<ResolveInfo> ris = mPackageManager.queryBroadcastReceivers(i, PackageManager.GET_DISABLED_COMPONENTS);
217             setupReceivers(ris, defPackage);
218             // The open and close receivers are likely the same, but they may not be.
219             i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
220             ris = mPackageManager.queryBroadcastReceivers(i, PackageManager.GET_DISABLED_COMPONENTS);
221             setupReceivers(ris, defPackage);
222 
223             // Write the selected default to the prefs so that the Redirector activity
224             // knows which one to use.
225             SharedPreferences pref = getSharedPreferences("musicfx", MODE_PRIVATE);
226             Editor ed = pref.edit();
227             ed.putString("defaultpanelpackage", defPackage);
228             ed.putString("defaultpanelname", defName);
229             ed.commit();
230             log("wrote " + defPackage + "/" + defName + " as default");
231         }
232 
setupReceivers(List<ResolveInfo> ris, String defPackage)233         private void setupReceivers(List<ResolveInfo> ris, String defPackage) {
234             // TODO - we may need to keep track of active sessions and send "open session"
235             // broadcast to newly enabled receivers, while sending "close session" to
236             // receivers that are about to be disabled. We could also consider just
237             // killing the process hosting the disabled components.
238             for (ResolveInfo foo: ris) {
239                 ComponentName comp = new ComponentName(foo.activityInfo.packageName, foo.activityInfo.name);
240                 if (foo.activityInfo.packageName.equals(defPackage)) {
241                     log("enabling receiver " + foo);
242                     mPackageManager.setComponentEnabledSetting(comp,
243                             PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
244                             PackageManager.DONT_KILL_APP);
245                 } else {
246                     log("disabling receiver " + foo);
247                     mPackageManager.setComponentEnabledSetting(comp,
248                             PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
249                             PackageManager.DONT_KILL_APP);
250                 }
251             }
252         }
253     }
254 
log(String out)255     private static void log(String out) {
256         if (LOG) {
257             Log.d(TAG, out);
258         }
259     }
260 }
261