Merge "Introduce SystemUI-managed alternative system bars."
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c1a296d..1d2fbd8 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4089,6 +4089,9 @@
          */
         public static final String DIALPAD_AUTOCOMPLETE = "dialpad_autocomplete";
 
+        /** @hide */
+        public static final String BAR_SERVICE_COMPONENT = "bar_service_component";
+
         /**
          * This are the settings to be backed up.
          *
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 043b64c..f130993 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -25,17 +25,14 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
 public class SystemUIService extends Service {
-    static final String TAG = "SystemUIService";
+    private static final String TAG = "SystemUIService";
 
     /**
-     * The class names of the stuff to start.
+     * The classes of the stuff to start.
      */
-    final Object[] SERVICES = new Object[] {
-            R.string.config_statusBarComponent,
+    private final Class<?>[] SERVICES = new Class[] {
+            com.android.systemui.statusbar.SystemBars.class,
             com.android.systemui.power.PowerUI.class,
             com.android.systemui.media.RingtonePlayer.class,
             com.android.systemui.settings.SettingsUI.class,
@@ -44,29 +41,13 @@
     /**
      * Hold a reference on the stuff we start.
      */
-    SystemUI[] mServices;
-
-    private Class chooseClass(Object o) {
-        if (o instanceof Integer) {
-            final String cl = getString((Integer)o);
-            try {
-                return getClassLoader().loadClass(cl);
-            } catch (ClassNotFoundException ex) {
-                throw new RuntimeException(ex);
-            }
-        } else if (o instanceof Class) {
-            return (Class)o;
-        } else {
-            throw new RuntimeException("Unknown system ui service: " + o);
-        }
-    }
+    private final SystemUI[] mServices = new SystemUI[SERVICES.length];
 
     @Override
     public void onCreate() {
         final int N = SERVICES.length;
-        mServices = new SystemUI[N];
         for (int i=0; i<N; i++) {
-            Class cl = chooseClass(SERVICES[i]);
+            Class<?> cl = SERVICES[i];
             Log.d(TAG, "loading: " + cl);
             try {
                 mServices[i] = (SystemUI)cl.newInstance();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 0879e0f..bf82466 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -194,6 +194,18 @@
         }
     };
 
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+                userSwitched(mCurrentUserId);
+            }
+        }
+    };
+
     public void start() {
         mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
         mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
@@ -271,16 +283,7 @@
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
-                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                    mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                    if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
-                    userSwitched(mCurrentUserId);
-                }
-            }}, filter);
+        mContext.registerReceiver(mBroadcastReceiver, filter);
 
         mLocale = mContext.getResources().getConfiguration().locale;
     }
@@ -1177,4 +1180,11 @@
     public void resumeAutohide() {
         // hook for subclasses
     }
+
+    public void destroy() {
+        if (mSearchPanelView != null) {
+            mWindowManager.removeViewImmediate(mSearchPanelView);
+        }
+        mContext.unregisterReceiver(mBroadcastReceiver);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java
new file mode 100644
index 0000000..f763f03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java
@@ -0,0 +1,273 @@
+/*
+ * 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();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SystemBars.java b/packages/SystemUI/src/com/android/systemui/statusbar/SystemBars.java
new file mode 100644
index 0000000..847bf96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SystemBars.java
@@ -0,0 +1,95 @@
+/*
+ * 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);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index d86760f..dca8c97e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -2691,4 +2691,16 @@
         public void setBounds(Rect bounds) {
         }
     }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+        if (mStatusBarWindow != null) {
+            mWindowManager.removeViewImmediate(mStatusBarWindow);
+        }
+        if (mNavigationBarView != null) {
+            mWindowManager.removeViewImmediate(mNavigationBarView);
+        }
+        mContext.unregisterReceiver(mBroadcastReceiver);
+    }
 }