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                 // use the built-in panel
73                 i.setComponent(new ComponentName(this, ActivityMusic.class));
74                 // also save it as the default
75                 Intent updateIntent = new Intent(this, Service.class);
76                 updateIntent.putExtra("defPackage", getPackageName());
77                 updateIntent.putExtra("defName", ActivityMusic.class.getName());
78                 startService(updateIntent);
79             } else {
80                 i.setComponent(new ComponentName(defPackage, defName));
81             }
82             startActivity(i);
83             finish();
84         }
85     }
86 
87     /**
88      * This BroadcastReceiver responds to BOOT_COMPLETED, PACKAGE_ADDED,
89      * PACKAGE_REPLACED and PACKAGE_REMOVED intents. When run, it checks
90      * to see whether the active control panel needs to be updated:
91      * - if there is no default, it picks one
92      * - if a new control panel is installed, it becomes the default
93      * It then enables the open/close receivers in the active control panel,
94      * and disables them in the others.
95      */
96     public static class Receiver extends BroadcastReceiver {
97 
98         @Override
onReceive(final Context context, final Intent intent)99         public void onReceive(final Context context, final Intent intent) {
100 
101             log("received");
102             Intent updateIntent = new Intent(context, Service.class);
103             updateIntent.putExtra("reason", intent);
104             context.startService(updateIntent);
105         }
106     }
107 
108     public static class Service extends IntentService {
109 
110         PackageManager mPackageManager;
111 
Service()112         public Service() {
113             super("CompatibilityService");
114         }
115 
116         @Override
onHandleIntent(final Intent intent)117         protected void onHandleIntent(final Intent intent) {
118             log("handleintent");
119             if (mPackageManager == null) {
120                 mPackageManager = getPackageManager();
121             }
122 
123             String defPackage = intent.getStringExtra("defPackage");
124             String defName = intent.getStringExtra("defName");
125             if (defPackage != null && defName != null) {
126                 setDefault(defPackage, defName);
127                 return;
128             }
129 
130             Intent packageIntent = intent.getParcelableExtra("reason");
131             Bundle b = packageIntent.getExtras();
132             if (b != null) b.size();
133             log("intentservice saw: " + packageIntent + " " + b);
134             // TODO, be smarter about package upgrades (which results in three
135             // broadcasts: removed, added, replaced)
136             Uri packageUri = packageIntent.getData();
137             String updatedPackage = null;
138             if (packageUri != null) {
139                 updatedPackage = packageUri.toString().substring(8);
140                 pickDefaultControlPanel(updatedPackage);
141             }
142         }
143 
pickDefaultControlPanel(String updatedPackage)144         private void pickDefaultControlPanel(String updatedPackage) {
145 
146             ResolveInfo defPanel = null;
147             ResolveInfo otherPanel = null;
148             ResolveInfo thisPanel = null;
149             Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
150             List<ResolveInfo> ris = mPackageManager.queryIntentActivities(i, PackageManager.GET_DISABLED_COMPONENTS);
151             log("found: " + ris.size());
152             SharedPreferences pref = getSharedPreferences("musicfx", MODE_PRIVATE);
153             String savedDefPackage = pref.getString("defaultpanelpackage", null);
154             String savedDefName = pref.getString("defaultpanelname", null);
155             log("saved default: " + savedDefName);
156             for (ResolveInfo foo: ris) {
157                 if (foo.activityInfo.name.equals(Compatibility.Redirector.class.getName())) {
158                     log("skipping " + foo);
159                     continue;
160                 }
161                 log("considering " + foo);
162                 if (foo.activityInfo.name.equals(savedDefName) &&
163                         foo.activityInfo.packageName.equals(savedDefPackage) &&
164                         foo.activityInfo.enabled) {
165                     log("default: " + savedDefName);
166                     defPanel = foo;
167                     break;
168                 } else if (foo.activityInfo.packageName.equals(updatedPackage)) {
169                     log("choosing newly installed package " + updatedPackage);
170                     otherPanel = foo;
171                 } else if (otherPanel == null && !foo.activityInfo.packageName.equals(getPackageName())) {
172                     otherPanel = foo;
173                 } else {
174                     thisPanel = foo;
175                 }
176             }
177 
178             if (defPanel == null) {
179                 // pick a default control panel
180                 if (otherPanel == null) {
181                     if (thisPanel == null) {
182                         Log.e(TAG, "No control panels found!");
183                         return;
184                     }
185                     otherPanel = thisPanel;
186                 }
187                 defPanel = otherPanel;
188             }
189 
190             // Now that we have selected a default control panel activity, ensure
191             // that the broadcast receiver(s) in that same package are enabled,
192             // and the ones in the other packages are disabled.
193             String defPackage = defPanel.activityInfo.packageName;
194             String defName = defPanel.activityInfo.name;
195             setDefault(defPackage, defName);
196         }
197 
setDefault(String defPackage, String defName)198         private void setDefault(String defPackage, String defName) {
199             Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
200             List<ResolveInfo> ris = mPackageManager.queryBroadcastReceivers(i, PackageManager.GET_DISABLED_COMPONENTS);
201             setupReceivers(ris, defPackage);
202             // The open and close receivers are likely the same, but they may not be.
203             i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
204             ris = mPackageManager.queryBroadcastReceivers(i, PackageManager.GET_DISABLED_COMPONENTS);
205             setupReceivers(ris, defPackage);
206 
207             // Write the selected default to the prefs so that the Redirector activity
208             // knows which one to use.
209             SharedPreferences pref = getSharedPreferences("musicfx", MODE_PRIVATE);
210             Editor ed = pref.edit();
211             ed.putString("defaultpanelpackage", defPackage);
212             ed.putString("defaultpanelname", defName);
213             ed.commit();
214             log("wrote " + defPackage + "/" + defName + " as default");
215         }
216 
setupReceivers(List<ResolveInfo> ris, String defPackage)217         private void setupReceivers(List<ResolveInfo> ris, String defPackage) {
218             // TODO - we may need to keep track of active sessions and send "open session"
219             // broadcast to newly enabled receivers, while sending "close session" to
220             // receivers that are about to be disabled. We could also consider just
221             // killing the process hosting the disabled components.
222             for (ResolveInfo foo: ris) {
223                 ComponentName comp = new ComponentName(foo.activityInfo.packageName, foo.activityInfo.name);
224                 if (foo.activityInfo.packageName.equals(defPackage)) {
225                     log("enabling receiver " + foo);
226                     mPackageManager.setComponentEnabledSetting(comp,
227                             PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
228                             PackageManager.DONT_KILL_APP);
229                 } else {
230                     log("disabling receiver " + foo);
231                     mPackageManager.setComponentEnabledSetting(comp,
232                             PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
233                             PackageManager.DONT_KILL_APP);
234                 }
235             }
236         }
237     }
238 
log(String out)239     private static void log(String out) {
240         if (LOG) {
241             Log.d(TAG, out);
242         }
243     }
244 }
245