OSDN Git Service

Introduce SystemUI-managed alternative system bars.
authorJohn Spurlock <jspurlock@google.com>
Mon, 17 Jun 2013 11:35:46 +0000 (07:35 -0400)
committerJohn Spurlock <jspurlock@google.com>
Fri, 21 Jun 2013 21:31:25 +0000 (17:31 -0400)
If a service component is defined in a new secure setting,
SystemUI will attempt to use that service as the status bar
provider.

Falls back to the existing in-process implementation configured
in the product config if the setting is missing or invalid.

Nothing changes yet from a permission point of view.  Alternative
system bar implementations still require the status bar permission.

Also nothing changes from an api point of view.  Alternative
system bar implementations use the existing IStatusBar interface.

This simply enables testing alternative system bar implementations
installed from other trusted, platform-signed packages.

Known caveat: the setting is stored per user, multi-user changes
will be handled in a future CL.

Change-Id: I0413df185f7e75f77ad2ae1bc3689306d5e6e0fb

core/java/android/provider/Settings.java
packages/SystemUI/src/com/android/systemui/SystemUIService.java
packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/statusbar/SystemBars.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java

index c1a296d..1d2fbd8 100644 (file)
@@ -4089,6 +4089,9 @@ public final class Settings {
          */
         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.
          *
index 043b64c..f130993 100644 (file)
@@ -25,17 +25,14 @@ import android.util.Log;
 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 @@ public class SystemUIService extends Service {
     /**
      * 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();
index 0879e0f..bf82466 100644 (file)
@@ -194,6 +194,18 @@ public abstract class BaseStatusBar extends SystemUI implements
         }
     };
 
+    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 @@ public abstract class BaseStatusBar extends SystemUI implements
 
         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 abstract class BaseStatusBar extends SystemUI implements
     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 (file)
index 0000000..f763f03
--- /dev/null
@@ -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 (file)
index 0000000..847bf96
--- /dev/null
@@ -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);
+    }
+}
index d86760f..dca8c97 100644 (file)
@@ -2691,4 +2691,16 @@ public class PhoneStatusBar extends BaseStatusBar {
         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);
+    }
 }