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