Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e539ee86 authored by John Spurlock's avatar John Spurlock Committed by Android (Google) Code Review
Browse files

Merge "Introduce SystemUI-managed alternative system bars."

parents 4a7052a7 5c454124
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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.
         *
+6 −25
Original line number Diff line number Diff line
@@ -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();
+20 −10
Original line number Diff line number Diff line
@@ -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);
    }
}
+273 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}
+95 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
Loading