cmsdk: Create brokerablecmsystemservice concept.

  Extending the BrokerableCMSystemService allows a core
  system service to declare a delegate provider interface
  that can exist in another package, either in the same
  or an external process.

Change-Id: Idf8d170b1504528b0d3aafb23895951e26459c98
diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/BrokerableCMSystemService.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/BrokerableCMSystemService.java
new file mode 100644
index 0000000..76bad9f
--- /dev/null
+++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/BrokerableCMSystemService.java
@@ -0,0 +1,234 @@
+/**
+ * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.platform.internal;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import cyanogenmod.platform.Manifest;
+
+import com.android.internal.util.Preconditions;
+
+import org.cyanogenmod.platform.internal.common.BrokeredServiceConnection;
+
+public abstract class BrokerableCMSystemService<T extends IInterface> extends CMSystemService {
+    private static final String TAG = BrokerableCMSystemService.class.getSimpleName();
+
+    private static final int MSG_TRY_CONNECTING = 1;
+    private static final long SERVICE_CONNECTION_WAIT_TIME_MS = 4 * 1000L; // 4 seconds
+    private Context mContext;
+
+    private BrokeredServiceConnection mBrokeredServiceConnection;
+    private T mImplementingBinderInterface;
+
+    public BrokerableCMSystemService(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    /**
+     * Called when the {@link IInterface} contract for the given {@link IBinder} is to
+     * be assigned for the implementing service {@link T}
+     * @param service
+     * @return {@link T}
+     */
+    protected abstract T getIBinderAsIInterface(@NonNull IBinder service);
+
+    /**
+     * Called when necessary as the default implementation. (usually a failure implementation)
+     * For when an implementing service is not found, is updating, or is failing.
+     * @return {@link T}
+     */
+    protected abstract T getDefaultImplementation();
+
+    /**
+     * Called when attempting to connect to the a given {@link T} implementation. Defines
+     * the {@link ComponentName} to be used for the binding operation.
+     *
+     * By default, the calling component MUST gate its implementation by the
+     * {@link Manifest.permission.BIND_CORE_SERVICE} permission as well as using,
+     * the permission granted to its own package.
+     *
+     * This permission can be overridden via {@link #getComponentFilteringPermission()}
+     *
+     * @return {@link ComponentName}
+     */
+    @Nullable
+    protected abstract ComponentName getServiceComponent();
+
+    /**
+     * Override this method if your broker will provide its own permission for guarding a vertical
+     * api defintion. Otherwise, the component from {@link #getServiceComponent()}
+     * will be gated via the {@link Manifest.permission.BIND_CORE_SERVICE} permission.
+     *
+     * @return boolean
+     */
+    @NonNull
+    protected String getComponentFilteringPermission() {
+        return Manifest.permission.BIND_CORE_SERVICE;
+    }
+
+    /**
+     * Set a {@link BrokeredServiceConnection} to receive callbacks when an implementation is
+     * connected or disconnected.
+     * @param brokeredServiceComponent
+     */
+    public final void setBrokeredServiceConnection(
+            @NonNull BrokeredServiceConnection brokeredServiceComponent) {
+        Preconditions.checkNotNull(brokeredServiceComponent);
+        Slog.e(TAG, "Setting brokered service connection "
+                + brokeredServiceComponent.toString());
+        mBrokeredServiceConnection = brokeredServiceComponent;
+    }
+
+    /**
+     * Get the implementing service for the given binder invocation. Usually called from a binder
+     * thread in a subclassed service.
+     * @return {@link T} that represents the implementing service
+     */
+    public final T getBrokeredService() {
+        final T service = getOrConnectService();
+        if (service != null) {
+            return service;
+        }
+        return getDefaultImplementation();
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        super.onBootPhase(phase);
+        if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+            Slog.d(TAG, "Third party apps ready");
+            tryConnecting();
+        }
+    }
+
+    private T getOrConnectService() {
+        synchronized (this) {
+            if (mImplementingBinderInterface != null) {
+                return mImplementingBinderInterface;
+            }
+            // Service is not connected. Try blocking connecting.
+            mConnectionHandler.sendMessage(
+                    mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING));
+            final long shouldEnd =
+                    SystemClock.elapsedRealtime() + SERVICE_CONNECTION_WAIT_TIME_MS;
+            long waitTime = SERVICE_CONNECTION_WAIT_TIME_MS;
+            while (waitTime > 0) {
+                try {
+                    // TODO: consider using Java concurrent construct instead of raw object wait
+                    this.wait(waitTime);
+                } catch (InterruptedException e) {
+                    Slog.w(TAG, "Connection wait interrupted", e);
+                }
+                if (mImplementingBinderInterface != null) {
+                    // Success
+                    return mImplementingBinderInterface;
+                }
+                // Calculate remaining waiting time to make sure we wait the full timeout period
+                waitTime = shouldEnd - SystemClock.elapsedRealtime();
+            }
+            return null;
+        }
+    }
+
+    private final Handler mConnectionHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_TRY_CONNECTING:
+                    tryConnecting();
+                    break;
+                default:
+                    Slog.e(TAG, "Unknown message");
+            }
+        }
+    };
+
+    /**
+     * Attempt to connect to the component which is going to serve {@link T}
+     * interface contract implementation.
+     */
+    public final void tryConnecting() {
+        Slog.i(TAG, "Connecting to implementation");
+        synchronized (this) {
+            if (mImplementingBinderInterface != null) {
+                Slog.d(TAG, "Already connected");
+                return;
+            }
+            final Intent intent = new Intent();
+            final ComponentName cn = getServiceComponent();
+            if (cn == null) {
+                Slog.e(TAG, "No implementation service found");
+                return;
+            }
+            intent.setComponent(cn);
+            try {
+                if (mContext.getPackageManager().checkPermission(
+                        getComponentFilteringPermission(),
+                        cn.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
+                    Slog.e(TAG, "Target component lacks " + getComponentFilteringPermission()
+                            + " service permission, failing " + cn);
+                    return;
+                }
+                if (!mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
+                    Slog.e(TAG, "Failed to bind to implementation " + cn);
+                }
+            } catch (SecurityException e) {
+                Slog.e(TAG, "Forbidden to bind to implementation " + cn, e);
+            }
+        }
+    }
+
+    private ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Slog.i(TAG, "Implementation service connected");
+            synchronized (BrokerableCMSystemService.this) {
+                mImplementingBinderInterface = getIBinderAsIInterface(service);
+                BrokerableCMSystemService.this.notifyAll();
+                if (mBrokeredServiceConnection != null) {
+                    Slog.i(TAG, "Notifying service connected");
+                    mBrokeredServiceConnection.onBrokeredServiceConnected();
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Slog.i(TAG, "Implementation service unexpectedly disconnected");
+            synchronized (BrokerableCMSystemService.this) {
+                mImplementingBinderInterface = null;
+                BrokerableCMSystemService.this.notifyAll();
+                if (mBrokeredServiceConnection != null) {
+                    mBrokeredServiceConnection.onBrokeredServiceDisconnected();
+                }
+            }
+        }
+    };
+}
diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/LiveLockScreenServiceBroker.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/LiveLockScreenServiceBroker.java
index a142f1f..e133aa6 100644
--- a/cm/lib/main/java/org/cyanogenmod/platform/internal/LiveLockScreenServiceBroker.java
+++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/LiveLockScreenServiceBroker.java
@@ -26,6 +26,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IInterface;
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -44,6 +45,8 @@
 import cyanogenmod.platform.Manifest;
 import cyanogenmod.providers.CMSettings;
 
+import org.cyanogenmod.platform.internal.common.BrokeredServiceConnection;
+
 import java.util.List;
 
 /**
@@ -52,20 +55,15 @@
  *
  * @hide
  */
-public class LiveLockScreenServiceBroker extends CMSystemService {
+public class LiveLockScreenServiceBroker extends
+        BrokerableCMSystemService<ILiveLockScreenManagerProvider> {
     private static final String TAG = LiveLockScreenServiceBroker.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    private static final int MSG_TRY_CONNECTING = 1;
-
-    private static final long SERVICE_CONNECTION_WAIT_TIME_MS = 4 * 1000L; // 4 seconds
-
     private static final String DEPRECATED_THIRD_PARTY_KEYGUARD_PERMISSION =
             "android.permission.THIRD_PARTY_KEYGUARD";
 
     private Context mContext;
-    // The actual LLS service to invoke
-    private ILiveLockScreenManagerProvider mService;
 
     // Cached change listeners
     private final RemoteCallbackList<ILiveLockScreenChangeListener> mChangeListeners =
@@ -73,53 +71,6 @@
 
     private LiveLockScreenInfo mDefaultLlsInfo;
 
-    private final Handler mConnectionHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_TRY_CONNECTING:
-                    tryConnecting();
-                    break;
-                default:
-                    Slog.e(TAG, "Unknown message");
-            }
-        }
-    };
-
-    private ServiceConnection mConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Slog.i(TAG, "LiveLockScreenManagerService connected");
-            synchronized (LiveLockScreenServiceBroker.this) {
-                mService = ILiveLockScreenManagerProvider.Stub.asInterface(service);
-                LiveLockScreenServiceBroker.this.notifyAll();
-                // If any change listeners are cached, register them with the newly connected
-                // service.
-                try {
-                    int N = mChangeListeners.beginBroadcast();
-                    if (mService != null && N > 0) {
-                        for (int i = 0; i < N; i++) {
-                            mService.registerChangeListener(mChangeListeners.getBroadcastItem(i));
-                        }
-                    }
-                } catch (RemoteException e) {
-                    /* ignore */
-                } finally {
-                    mChangeListeners.finishBroadcast();
-                }
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Slog.i(TAG, "LiveLockScreenManagerService unexpectedly disconnected");
-            synchronized (LiveLockScreenServiceBroker.this) {
-                mService = null;
-                LiveLockScreenServiceBroker.this.notifyAll();
-            }
-        }
-    };
-
     /**
      * ILiveLockScreenManager implementation to use when no backing service can be found.
      */
@@ -172,17 +123,17 @@
         @Override
         public void enqueueLiveLockScreen(String pkg, int id,
                 LiveLockScreenInfo lls, int[] idReceived, int userId) throws RemoteException {
-            getServiceGuarded().enqueueLiveLockScreen(pkg, id, lls, idReceived, userId);
+            getBrokeredService().enqueueLiveLockScreen(pkg, id, lls, idReceived, userId);
         }
 
         @Override
         public void cancelLiveLockScreen(String pkg, int id, int userId) throws RemoteException {
-            getServiceGuarded().cancelLiveLockScreen(pkg, id, userId);
+            getBrokeredService().cancelLiveLockScreen(pkg, id, userId);
         }
 
         @Override
         public LiveLockScreenInfo getCurrentLiveLockScreen() throws RemoteException {
-            return getServiceGuarded().getCurrentLiveLockScreen();
+            return getBrokeredService().getCurrentLiveLockScreen();
         }
 
         @Override
@@ -205,23 +156,23 @@
 
         @Override
         public boolean getLiveLockScreenEnabled() throws RemoteException {
-            return getServiceGuarded().getLiveLockScreenEnabled();
+            return getBrokeredService().getLiveLockScreenEnabled();
         }
 
         @Override
         public boolean registerChangeListener(
                 ILiveLockScreenChangeListener listener) throws RemoteException {
-            boolean registered = getServiceGuarded().registerChangeListener(listener);
+            boolean registered = getBrokeredService().registerChangeListener(listener);
             if (registered) {
                 mChangeListeners.register(listener);
             }
-            return getServiceGuarded().registerChangeListener(listener);
+            return registered;
         }
 
         @Override
         public boolean unregisterChangeListener(
                 ILiveLockScreenChangeListener listener) throws RemoteException {
-            boolean unregistered = getServiceGuarded().unregisterChangeListener(listener);
+            boolean unregistered = getBrokeredService().unregisterChangeListener(listener);
             if (unregistered) {
                 mChangeListeners.unregister(listener);
             }
@@ -232,6 +183,7 @@
     public LiveLockScreenServiceBroker(Context context) {
         super(context);
         mContext = context;
+        setBrokeredServiceConnection(mServiceConnection);
     }
 
     @Override
@@ -246,6 +198,65 @@
     }
 
     @Override
+    protected ILiveLockScreenManagerProvider getIBinderAsIInterface(IBinder service) {
+        return ILiveLockScreenManagerProvider.Stub.asInterface(service);
+    }
+
+    @Override
+    public ILiveLockScreenManagerProvider getDefaultImplementation() {
+        return mServiceStubForFailure;
+    }
+
+    private BrokeredServiceConnection mServiceConnection = new BrokeredServiceConnection() {
+        @Override
+        public void onBrokeredServiceConnected() {
+            // If any change listeners are cached, register them with the newly connected
+            // service.
+            try {
+                int N = mChangeListeners.beginBroadcast();
+                ILiveLockScreenManagerProvider iLiveLockScreenManagerProvider =
+                        getBrokeredService();
+                if (iLiveLockScreenManagerProvider != null && N > 0) {
+                    for (int i = 0; i < N; i++) {
+                        iLiveLockScreenManagerProvider
+                                .registerChangeListener(mChangeListeners.getBroadcastItem(i));
+                    }
+                }
+            } catch (RemoteException e) {
+                    /* ignore */
+            } finally {
+                mChangeListeners.finishBroadcast();
+            }
+        }
+
+        @Override
+        public void onBrokeredServiceDisconnected() {
+
+        }
+    };
+
+    @Override
+    protected String getComponentFilteringPermission() {
+        // Live lock screen service has its own vertical providing permission
+        return Manifest.permission.LIVE_LOCK_SCREEN_MANAGER_PROVIDER;
+    }
+
+    @Override
+    protected ComponentName getServiceComponent() {
+        PackageManager pm = mContext.getPackageManager();
+        Intent intent = new Intent(LiveLockScreenManager.SERVICE_INTERFACE);
+        List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 0);
+        for (ResolveInfo info : resolveInfos) {
+            if (info != null) {
+                if (info.serviceInfo.isEnabled()) {
+                    return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
     public void onBootPhase(int phase) {
         if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             if (DEBUG) Slog.d(TAG, "Third party apps ready");
@@ -258,109 +269,8 @@
                         .setComponent(ComponentName.unflattenFromString(defComponent))
                         .build();
             }
-            // Now that 3rd party apps are ready, try connecting to the backing service
-            tryConnecting();
         }
-    }
-
-    /**
-     * Binds to the backing service if one is found
-     */
-    private void tryConnecting() {
-        Slog.i(TAG, "Connecting to LiveLockScreenManagerService");
-        synchronized (this) {
-            if (mService != null) {
-                Slog.d(TAG, "Already connected");
-                return;
-            }
-            final Intent intent = new Intent();
-            final ComponentName cn = getLiveLockScreenServiceComponent();
-            if (cn == null) {
-                Slog.e(TAG, "No live lock screen manager service found");
-                return;
-            }
-            intent.setComponent(getLiveLockScreenServiceComponent());
-            try {
-                if (!mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
-                    Slog.e(TAG, "Failed to bind to LiveLockScreenManagerService");
-                }
-            } catch (SecurityException e) {
-                Slog.e(TAG, "Forbidden to bind to LiveLockScreenManagerService", e);
-            }
-        }
-    }
-
-    /**
-     * Queries package manager for the component which handles the
-     * {@link LiveLockScreenManager#SERVICE_INTERFACE} and has been granted the
-     * {@link org.cyanogenmod.platform.internal.Manifest.permission#LIVE_LOCK_SCREEN_MANAGER_PROVIDER}
-     * permission.
-     *
-     * @return A valid component that supports {@link LiveLockScreenManager#SERVICE_INTERFACE} or
-     *         null if no component can be found.
-     */
-    @Nullable private ComponentName getLiveLockScreenServiceComponent() {
-        PackageManager pm = mContext.getPackageManager();
-        Intent intent = new Intent(LiveLockScreenManager.SERVICE_INTERFACE);
-        List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 0);
-        for (ResolveInfo info : resolveInfos) {
-            if (info != null) {
-                if (pm.checkPermission(Manifest.permission.LIVE_LOCK_SCREEN_MANAGER_PROVIDER,
-                        info.serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED &&
-                        info.serviceInfo.isEnabled()) {
-                    return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
-                }
-            }
-        }
-
-        return null;
-    }
-
-    private ILiveLockScreenManagerProvider getOrConnectService() {
-        synchronized (this) {
-            if (mService != null) {
-                return mService;
-            }
-            // Service is not connected. Try blocking connecting.
-            Slog.w(TAG, "LiveLockScreenManagerService not connected. Try connecting...");
-            mConnectionHandler.sendMessage(
-                    mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING));
-            final long shouldEnd =
-                    SystemClock.elapsedRealtime() + SERVICE_CONNECTION_WAIT_TIME_MS;
-            long waitTime = SERVICE_CONNECTION_WAIT_TIME_MS;
-            while (waitTime > 0) {
-                try {
-                    // TODO: consider using Java concurrent construct instead of raw object wait
-                    this.wait(waitTime);
-                } catch (InterruptedException e) {
-                    Slog.w(TAG, "Connection wait interrupted", e);
-                }
-                if (mService != null) {
-                    // Success
-                    return mService;
-                }
-                // Calculate remaining waiting time to make sure we wait the full timeout period
-                waitTime = shouldEnd - SystemClock.elapsedRealtime();
-            }
-            // Timed out. Something's really wrong.
-            Slog.e(TAG, "Can not connect to LiveLockScreenManagerService (timed out)");
-            return null;
-        }
-    }
-
-    /**
-     * Make sure to return a non-empty service instance. Return the connected LiveLockScreenManager
-     * instance, if not connected, try connecting. If fail to connect, return a fake service
-     * instance which returns failure to service caller.
-     *
-     * @return a non-empty service instance, real or fake
-     */
-    private ILiveLockScreenManagerProvider getServiceGuarded() {
-        final ILiveLockScreenManagerProvider service = getOrConnectService();
-        if (service != null) {
-            return service;
-        }
-        return mServiceStubForFailure;
+        super.onBootPhase(phase);
     }
 
     /**
@@ -406,7 +316,7 @@
 
         mDefaultLlsInfo = llsInfo;
         try {
-            getServiceGuarded().updateDefaultLiveLockScreen(llsInfo);
+            getBrokeredService().updateDefaultLiveLockScreen(llsInfo);
         } catch (RemoteException e) {
             /* ignore */
         }
diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/common/BrokeredServiceConnection.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/common/BrokeredServiceConnection.java
new file mode 100644
index 0000000..6775937
--- /dev/null
+++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/common/BrokeredServiceConnection.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.platform.internal.common;
+
+/**
+ * The {@link BrokeredServiceConnection} provides callbacks for when a
+ * {@link org.cyanogenmod.platform.internal.BrokerableCMSystemService} interface
+ * contract is either fulfilled and connected to an implementation or disconnected.
+ */
+public interface BrokeredServiceConnection {
+
+    /**
+     * Callback that signifies that the given interface contract passed into the
+     * {@link org.cyanogenmod.platform.internal.BrokerableCMSystemService} is implemented
+     * and connected.
+     */
+    void onBrokeredServiceConnected();
+
+    /**
+     * Callback that signifies that the given implementation for the interface contract passed
+     * into the {@link org.cyanogenmod.platform.internal.BrokerableCMSystemService} is disconnected.
+     */
+    void onBrokeredServiceDisconnected();
+}
diff --git a/cm/res/AndroidManifest.xml b/cm/res/AndroidManifest.xml
index 6468722..de6414f 100644
--- a/cm/res/AndroidManifest.xml
+++ b/cm/res/AndroidManifest.xml
@@ -220,7 +220,6 @@
                 android:description="@string/permdesc_observe_audio_sessions"
                 android:protectionLevel="normal"/>
 
-
     <!-- Allows an application to access the weather service.
         <p>Although the protection is normal, this permission should be required ONLY by those apps
         meant to do something meaningful with the data provided by the service (LockClock, SysUI)-->
@@ -236,6 +235,11 @@
                 android:icon="@drawable/ic_launcher_cyanogenmod"
                 android:protectionLevel="normal" />
 
+    <!-- Not meant for third parties. Allows brokering core services to other
+         processes @hide -->
+    <permission android:name="cyanogenmod.permission.BIND_CORE_SERVICE"
+                android:protectionLevel="signature|privileged" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"