diff options
author | Adnan Begovic <adnan@cyngn.com> | 2016-06-21 13:55:18 -0700 |
---|---|---|
committer | Adnan Begovic <adnan@cyngn.com> | 2016-07-01 10:41:53 -0700 |
commit | b62a4550c300171dd8845831393bab2209814460 (patch) | |
tree | 4865e16a5976123e8975d9b41e70698323628352 | |
parent | 551b377da9a3e92f5103a52b0b92ad72e72d6f89 (diff) | |
download | vendor_cmsdk-b62a4550c300171dd8845831393bab2209814460.zip vendor_cmsdk-b62a4550c300171dd8845831393bab2209814460.tar.gz vendor_cmsdk-b62a4550c300171dd8845831393bab2209814460.tar.bz2 |
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
4 files changed, 341 insertions, 155 deletions
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.content.pm.ResolveInfo; 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.app.LiveLockScreenManager; import cyanogenmod.platform.Manifest; import cyanogenmod.providers.CMSettings; +import org.cyanogenmod.platform.internal.common.BrokeredServiceConnection; + import java.util.List; /** @@ -52,20 +55,15 @@ import java.util.List; * * @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 @@ public class LiveLockScreenServiceBroker extends CMSystemService { 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 @@ public class LiveLockScreenServiceBroker extends CMSystemService { @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 @@ public class LiveLockScreenServiceBroker extends CMSystemService { @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 class LiveLockScreenServiceBroker extends CMSystemService { public LiveLockScreenServiceBroker(Context context) { super(context); mContext = context; + setBrokeredServiceConnection(mServiceConnection); } @Override @@ -246,121 +198,79 @@ public class LiveLockScreenServiceBroker extends CMSystemService { } @Override - public void onBootPhase(int phase) { - if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { - if (DEBUG) Slog.d(TAG, "Third party apps ready"); + protected ILiveLockScreenManagerProvider getIBinderAsIInterface(IBinder service) { + return ILiveLockScreenManagerProvider.Stub.asInterface(service); + } - // Initialize the default LLS component - String defComponent = CMSettings.Secure.getString(mContext.getContentResolver(), - CMSettings.Secure.DEFAULT_LIVE_LOCK_SCREEN_COMPONENT); - if (!TextUtils.isEmpty(defComponent)) { - mDefaultLlsInfo = new LiveLockScreenInfo.Builder() - .setComponent(ComponentName.unflattenFromString(defComponent)) - .build(); - } - // Now that 3rd party apps are ready, try connecting to the backing service - tryConnecting(); - } + @Override + public ILiveLockScreenManagerProvider getDefaultImplementation() { + return mServiceStubForFailure; } - /** - * 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()); + private BrokeredServiceConnection mServiceConnection = new BrokeredServiceConnection() { + @Override + public void onBrokeredServiceConnected() { + // If any change listeners are cached, register them with the newly connected + // service. try { - if (!mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) { - Slog.e(TAG, "Failed to bind to LiveLockScreenManagerService"); + 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 (SecurityException e) { - Slog.e(TAG, "Forbidden to bind to LiveLockScreenManagerService", e); + } 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; } - /** - * 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() { + @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 (pm.checkPermission(Manifest.permission.LIVE_LOCK_SCREEN_MANAGER_PROVIDER, - info.serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED && - info.serviceInfo.isEnabled()) { + if (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; - } - } + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { + if (DEBUG) Slog.d(TAG, "Third party apps ready"); - /** - * 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; + // Initialize the default LLS component + String defComponent = CMSettings.Secure.getString(mContext.getContentResolver(), + CMSettings.Secure.DEFAULT_LIVE_LOCK_SCREEN_COMPONENT); + if (!TextUtils.isEmpty(defComponent)) { + mDefaultLlsInfo = new LiveLockScreenInfo.Builder() + .setComponent(ComponentName.unflattenFromString(defComponent)) + .build(); + } } - return mServiceStubForFailure; + super.onBootPhase(phase); } /** @@ -406,7 +316,7 @@ public class LiveLockScreenServiceBroker extends CMSystemService { 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" |