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
17package com.android.musicfx;
18
19import android.app.Activity;
20import android.app.IntentService;
21import android.content.BroadcastReceiver;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.SharedPreferences;
26import android.content.SharedPreferences.Editor;
27import android.content.pm.PackageManager;
28import android.content.pm.ResolveInfo;
29import android.media.audiofx.AudioEffect;
30import android.net.Uri;
31import android.os.Bundle;
32import android.util.Log;
33
34import 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 */
46public 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
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
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
112        public Service() {
113            super("CompatibilityService");
114        }
115
116        @Override
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
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
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
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
239    private static void log(String out) {
240        if (LOG) {
241            Log.d(TAG, out);
242        }
243    }
244}
245