--- /dev/null
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Manages a persistent connection to a service component defined in a secure setting.
+ *
+ * <p>If a valid service component is specified in the secure setting, starts it up and keeps it
+ * running; handling setting changes, package updates, component disabling, and unexpected
+ * process termination.
+ *
+ * <p>Clients can listen for important events using the supplied {@link Callbacks}.
+ */
+public class ServiceMonitor {
+ private static final int RECHECK_DELAY = 2000;
+ private static final int WAIT_FOR_STOP = 500;
+
+ public interface Callbacks {
+ /** The service does not exist or failed to bind */
+ void onNoService();
+ /** The service is about to start, this is a chance to perform cleanup and
+ * delay the start if necessary */
+ long onServiceStartAttempt();
+ }
+
+ // internal handler + messages used to serialize access to internal state
+ public static final int MSG_START_SERVICE = 1;
+ public static final int MSG_CONTINUE_START_SERVICE = 2;
+ public static final int MSG_STOP_SERVICE = 3;
+ public static final int MSG_PACKAGE_INTENT = 4;
+ public static final int MSG_CHECK_BOUND = 5;
+ public static final int MSG_SERVICE_DISCONNECTED = 6;
+
+ private final Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_START_SERVICE:
+ startService();
+ break;
+ case MSG_CONTINUE_START_SERVICE:
+ continueStartService();
+ break;
+ case MSG_STOP_SERVICE:
+ stopService();
+ break;
+ case MSG_PACKAGE_INTENT:
+ packageIntent((Intent)msg.obj);
+ break;
+ case MSG_CHECK_BOUND:
+ checkBound();
+ break;
+ case MSG_SERVICE_DISCONNECTED:
+ serviceDisconnected((ComponentName)msg.obj);
+ break;
+ }
+ }
+ };
+
+ private final ContentObserver mSettingObserver = new ContentObserver(mHandler) {
+ public void onChange(boolean selfChange) {
+ onChange(selfChange, null);
+ }
+
+ public void onChange(boolean selfChange, Uri uri) {
+ if (mDebug) Log.d(mTag, "onChange selfChange=" + selfChange + " uri=" + uri);
+ if (mBound) {
+ mHandler.sendEmptyMessage(MSG_STOP_SERVICE);
+ }
+ mHandler.sendEmptyMessageDelayed(MSG_START_SERVICE, WAIT_FOR_STOP);
+ }
+ };
+
+ private final class SC implements ServiceConnection, IBinder.DeathRecipient {
+ private ComponentName mName;
+ private IBinder mService;
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (mDebug) Log.d(mTag, "onServiceConnected name=" + name + " service=" + service);
+ mName = name;
+ mService = service;
+ try {
+ service.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Log.w(mTag, "Error linking to death", e);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ if (mDebug) Log.d(mTag, "onServiceDisconnected name=" + name);
+ boolean unlinked = mService.unlinkToDeath(this, 0);
+ if (mDebug) Log.d(mTag, " unlinked=" + unlinked);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_SERVICE_DISCONNECTED, mName));
+ }
+
+ public void binderDied() {
+ if (mDebug) Log.d(mTag, "binderDied");
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_SERVICE_DISCONNECTED, mName));
+ }
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String pkg = intent.getData().getSchemeSpecificPart();
+ if (mServiceName != null && mServiceName.getPackageName().equals(pkg)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_PACKAGE_INTENT, intent));
+ }
+ }
+ };
+
+ private final String mTag;
+ private final boolean mDebug;
+
+ private final Context mContext;
+ private final String mSettingKey;
+ private final Callbacks mCallbacks;
+
+ private ComponentName mServiceName;
+ private SC mServiceConnection;
+ private boolean mBound;
+
+ public ServiceMonitor(String ownerTag, boolean debug,
+ Context context, String settingKey, Callbacks callbacks) {
+ mTag = ownerTag + ".ServiceMonitor";
+ mDebug = debug;
+ mContext = context;
+ mSettingKey = settingKey;
+ mCallbacks = callbacks;
+ }
+
+ public void start() {
+ // listen for setting changes
+ ContentResolver cr = mContext.getContentResolver();
+ cr.registerContentObserver(Settings.Secure.getUriFor(mSettingKey),
+ false /*notifyForDescendents*/, mSettingObserver, UserHandle.USER_ALL);
+
+ // listen for package/component changes
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(mBroadcastReceiver, filter);
+
+ mHandler.sendEmptyMessage(MSG_START_SERVICE);
+ }
+
+ // everything below is called on the handler
+
+ private void packageIntent(Intent intent) {
+ if (mDebug) Log.d(mTag, "packageIntent intent=" + intent
+ + " extras=" + bundleToString(intent.getExtras()));
+ if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
+ mHandler.sendEmptyMessage(MSG_START_SERVICE);
+ } else if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
+ PackageManager pm = mContext.getPackageManager();
+ boolean serviceEnabled =
+ pm.getApplicationEnabledSetting(mServiceName.getPackageName())
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ && pm.getComponentEnabledSetting(mServiceName)
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ if (mBound && !serviceEnabled) {
+ stopService();
+ scheduleCheckBound();
+ } else if (!mBound && serviceEnabled) {
+ startService();
+ }
+ }
+ }
+
+ private void stopService() {
+ if (mDebug) Log.d(mTag, "stopService");
+ boolean stopped = mContext.stopService(new Intent().setComponent(mServiceName));
+ if (mDebug) Log.d(mTag, " stopped=" + stopped);
+ mContext.unbindService(mServiceConnection);
+ mBound = false;
+ }
+
+ private void startService() {
+ String cn = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ mSettingKey, UserHandle.USER_CURRENT);
+ mServiceName = cn == null ? null : ComponentName.unflattenFromString(cn);
+ if (mDebug) Log.d(mTag, "startService mServiceName=" + mServiceName);
+ if (mServiceName == null) {
+ mBound = false;
+ mCallbacks.onNoService();
+ } else {
+ long delay = mCallbacks.onServiceStartAttempt();
+ mHandler.sendEmptyMessageDelayed(MSG_CONTINUE_START_SERVICE, delay);
+ }
+ }
+
+ private void continueStartService() {
+ if (mDebug) Log.d(mTag, "continueStartService");
+ Intent intent = new Intent().setComponent(mServiceName);
+ try {
+ mServiceConnection = new SC();
+ mBound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ if (mDebug) Log.d(mTag, "mBound: " + mBound);
+ } catch (Throwable t) {
+ Log.w(mTag, "Error binding to service: " + mServiceName, t);
+ }
+ if (!mBound) {
+ mCallbacks.onNoService();
+ }
+ }
+
+ private void serviceDisconnected(ComponentName serviceName) {
+ if (mDebug) Log.d(mTag, "serviceDisconnected serviceName=" + serviceName
+ + " mServiceName=" + mServiceName);
+ if (serviceName.equals(mServiceName)) {
+ mBound = false;
+ scheduleCheckBound();
+ }
+ }
+
+ private void checkBound() {
+ if (mDebug) Log.d(mTag, "checkBound mBound=" + mBound);
+ if (!mBound) {
+ startService();
+ }
+ }
+
+ private void scheduleCheckBound() {
+ mHandler.removeMessages(MSG_CHECK_BOUND);
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_BOUND, RECHECK_DELAY);
+ }
+
+ private static String bundleToString(Bundle bundle) {
+ if (bundle == null) return null;
+ StringBuilder sb = new StringBuilder('{');
+ for (String key : bundle.keySet()) {
+ if (sb.length() > 1) sb.append(',');
+ Object v = bundle.get(key);
+ v = (v instanceof String[]) ? Arrays.asList((String[]) v) : v;
+ sb.append(key).append('=').append(v);
+ }
+ return sb.append('}').toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+
+/**
+ * Ensure a single status bar service implementation is running at all times.
+ *
+ * <p>The implementation either comes from a service component running in a remote process (defined
+ * using a secure setting), else falls back to using the in-process implementation according
+ * to the product config.
+ */
+public class SystemBars extends SystemUI implements ServiceMonitor.Callbacks {
+ private static final String TAG = "SystemBars";
+ private static final boolean DEBUG = true;
+ private static final int WAIT_FOR_BARS_TO_DIE = 500;
+
+ // manages the implementation coming from the remote process
+ private ServiceMonitor mServiceMonitor;
+
+ // in-process fallback implementation, per the product config
+ private BaseStatusBar mStatusBar;
+
+ @Override
+ public void start() {
+ if (DEBUG) Log.d(TAG, "start");
+ mServiceMonitor = new ServiceMonitor(TAG, DEBUG,
+ mContext, Settings.Secure.BAR_SERVICE_COMPONENT, this);
+ mServiceMonitor.start(); // will call onNoService if no remote service is found
+ }
+
+ @Override
+ public void onNoService() {
+ if (DEBUG) Log.d(TAG, "onNoService");
+ createStatusBarFromConfig(); // fallback to using an in-process implementation
+ }
+
+ @Override
+ public long onServiceStartAttempt() {
+ if (DEBUG) Log.d(TAG, "onServiceStartAttempt mStatusBar="+mStatusBar);
+ if (mStatusBar != null) {
+ // tear down the in-process version, we'll recreate it again if needed
+ mStatusBar.destroy();
+ mStatusBar = null;
+ return WAIT_FOR_BARS_TO_DIE;
+ }
+ return 0;
+ }
+
+ private void createStatusBarFromConfig() {
+ if (DEBUG) Log.d(TAG, "createStatusBarFromConfig");
+ final String clsName = mContext.getString(R.string.config_statusBarComponent);
+ if (clsName == null || clsName.length() == 0) {
+ throw andLog("No status bar component configured", null);
+ }
+ Class<?> cls = null;
+ try {
+ cls = mContext.getClassLoader().loadClass(clsName);
+ } catch (Throwable t) {
+ throw andLog("Error loading status bar component: " + clsName, t);
+ }
+ try {
+ mStatusBar = (BaseStatusBar) cls.newInstance();
+ } catch (Throwable t) {
+ throw andLog("Error creating status bar component: " + clsName, t);
+ }
+ mStatusBar.mContext = mContext;
+ mStatusBar.start();
+ if (DEBUG) Log.d(TAG, "started " + mStatusBar.getClass().getSimpleName());
+ }
+
+ private RuntimeException andLog(String msg, Throwable t) {
+ Log.w(TAG, msg, t);
+ throw new RuntimeException(msg, t);
+ }
+}