aboutsummaryrefslogtreecommitdiffstats
path: root/sdk/src/java/cyanogenmod/app
diff options
context:
space:
mode:
Diffstat (limited to 'sdk/src/java/cyanogenmod/app')
-rw-r--r--sdk/src/java/cyanogenmod/app/BaseLiveLockManagerService.java226
-rw-r--r--sdk/src/java/cyanogenmod/app/CMContextConstants.java218
-rw-r--r--sdk/src/java/cyanogenmod/app/CMStatusBarManager.java247
-rw-r--r--sdk/src/java/cyanogenmod/app/CMTelephonyManager.java358
-rw-r--r--sdk/src/java/cyanogenmod/app/CustomTile.aidl19
-rw-r--r--sdk/src/java/cyanogenmod/app/CustomTile.java1106
-rw-r--r--sdk/src/java/cyanogenmod/app/CustomTileListenerService.java232
-rw-r--r--sdk/src/java/cyanogenmod/app/ICMStatusBarManager.aidl38
-rw-r--r--sdk/src/java/cyanogenmod/app/ICMTelephonyManager.aidl38
-rw-r--r--sdk/src/java/cyanogenmod/app/ICustomTileListener.aidl29
-rw-r--r--sdk/src/java/cyanogenmod/app/ILiveLockScreenChangeListener.aidl27
-rw-r--r--sdk/src/java/cyanogenmod/app/ILiveLockScreenManager.aidl73
-rw-r--r--sdk/src/java/cyanogenmod/app/ILiveLockScreenManagerProvider.aidl65
-rw-r--r--sdk/src/java/cyanogenmod/app/IPartnerInterface.aidl30
-rw-r--r--sdk/src/java/cyanogenmod/app/IProfileManager.aidl51
-rw-r--r--sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.aidl19
-rw-r--r--sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.java189
-rw-r--r--sdk/src/java/cyanogenmod/app/LiveLockScreenManager.java182
-rw-r--r--sdk/src/java/cyanogenmod/app/PartnerInterface.java262
-rw-r--r--sdk/src/java/cyanogenmod/app/Profile.aidl19
-rwxr-xr-xsdk/src/java/cyanogenmod/app/Profile.java1365
-rw-r--r--sdk/src/java/cyanogenmod/app/ProfileGroup.java395
-rw-r--r--sdk/src/java/cyanogenmod/app/ProfileManager.java555
-rw-r--r--sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.aidl20
-rw-r--r--sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.java256
-rw-r--r--sdk/src/java/cyanogenmod/app/ThemeComponent.java38
-rw-r--r--sdk/src/java/cyanogenmod/app/ThemeVersion.java206
-rw-r--r--sdk/src/java/cyanogenmod/app/suggest/AppSuggestManager.java150
-rw-r--r--sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.aidl22
-rw-r--r--sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.java110
-rw-r--r--sdk/src/java/cyanogenmod/app/suggest/IAppSuggestManager.aidl30
-rw-r--r--sdk/src/java/cyanogenmod/app/suggest/IAppSuggestProvider.aidl30
32 files changed, 6605 insertions, 0 deletions
diff --git a/sdk/src/java/cyanogenmod/app/BaseLiveLockManagerService.java b/sdk/src/java/cyanogenmod/app/BaseLiveLockManagerService.java
new file mode 100644
index 0000000..feae94f
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/BaseLiveLockManagerService.java
@@ -0,0 +1,226 @@
+/*
+ * 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 cyanogenmod.app;
+
+import android.annotation.NonNull;
+import android.app.AppGlobals;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import cyanogenmod.platform.Manifest;
+
+/**
+ * Base Live lock screen manager service to be extended by applications that implement the
+ * {@link LiveLockScreenManager#SERVICE_INTERFACE}
+ *
+ * @hide
+ */
+abstract public class BaseLiveLockManagerService extends Service
+ implements ILiveLockScreenManagerProvider {
+ private static final String TAG = BaseLiveLockManagerService.class.getSimpleName();
+
+ private final RemoteCallbackList<ILiveLockScreenChangeListener> mChangeListeners =
+ new RemoteCallbackList<>();
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return mService;
+ }
+
+ @Override
+ public final IBinder asBinder() {
+ return mService;
+ }
+
+ @Override
+ abstract public void enqueueLiveLockScreen(String pkg, int id, LiveLockScreenInfo lls,
+ int[] idReceived, int userId) throws RemoteException;
+
+ @Override
+ abstract public void cancelLiveLockScreen(String pkg, int id, int userId)
+ throws RemoteException;
+
+ @Override
+ abstract public LiveLockScreenInfo getCurrentLiveLockScreen() throws RemoteException;
+
+ @Override
+ abstract public void updateDefaultLiveLockScreen(LiveLockScreenInfo llsInfo)
+ throws RemoteException;
+
+ @Override
+ public boolean getLiveLockScreenEnabled() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public final boolean registerChangeListener(
+ ILiveLockScreenChangeListener listener) throws RemoteException {
+ return mChangeListeners.register(listener);
+ }
+
+ @Override
+ public final boolean unregisterChangeListener(
+ ILiveLockScreenChangeListener listener) throws RemoteException {
+ return mChangeListeners.unregister(listener);
+ }
+
+ /**
+ * This method should be called whenever there is an update to the current Live lock screen
+ * to be displayed.
+ *
+ * @param llsInfo LiveLockScreenInfo for the current Live lock screen
+ */
+ protected final void notifyChangeListeners(LiveLockScreenInfo llsInfo) {
+ int N = mChangeListeners.beginBroadcast();
+ for (int i = 0; i < N; i++) {
+ ILiveLockScreenChangeListener listener = mChangeListeners.getBroadcastItem(i);
+ try {
+ listener.onLiveLockScreenChanged(llsInfo);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to notifiy change listener", e);
+ }
+ }
+ mChangeListeners.finishBroadcast();
+ }
+
+ /**
+ * Returns true if the caller has been granted the
+ * {@link cyanogenmod.platform.Manifest.permission#LIVE_LOCK_SCREEN_MANAGER_ACCESS_PRIVATE}
+ * permission.
+ *
+ * @return
+ */
+ private final boolean hasPrivatePermissions() {
+ return checkCallingPermission(Manifest.permission
+ .LIVE_LOCK_SCREEN_MANAGER_ACCESS_PRIVATE) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Enforces the {@link cyanogenmod.platform.Manifest.permission#LIVE_LOCK_SCREEN_MANAGER_ACCESS}
+ * permission.
+ */
+ protected final void enforceAccessPermission() {
+ if (hasPrivatePermissions()) return;
+
+ enforceCallingPermission(Manifest.permission.LIVE_LOCK_SCREEN_MANAGER_ACCESS,
+ null);
+ }
+
+ /**
+ * Enforces the
+ * {@link cyanogenmod.platform.Manifest.permission#LIVE_LOCK_SCREEN_MANAGER_ACCESS_PRIVATE}
+ * permission.
+ */
+ protected final void enforcePrivateAccessPermission() {
+ enforceCallingPermission(
+ Manifest.permission.LIVE_LOCK_SCREEN_MANAGER_ACCESS_PRIVATE, null);
+ }
+
+ /**
+ * Enforces the LLS being shown/canceled is from the calling package or from a system app that
+ * has the
+ * {@link cyanogenmod.platform.Manifest.permission#LIVE_LOCK_SCREEN_MANAGER_ACCESS_PRIVATE}
+ * permission.
+ *
+ * @param pkg Package name of caller
+ * @param llsInfo Live lock screen info with component to check
+ */
+ protected final void enforceSamePackageOrSystem(String pkg,
+ @NonNull LiveLockScreenInfo llsInfo) {
+ // only apps with the private permission can show/cancel live lock screens from other
+ // packages
+ if (hasPrivatePermissions()) return;
+
+ if (llsInfo.component != null && !llsInfo.component.getPackageName().equals(pkg)) {
+ throw new SecurityException("Modifying Live lock screen from different packages not " +
+ "allowed. Calling package: " + pkg + " LLS package: " +
+ llsInfo.component.getPackageName());
+ }
+
+ final int uid = Binder.getCallingUid();
+ try {
+ ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
+ pkg, 0, UserHandle.getCallingUserId());
+ if (ai == null) {
+ throw new SecurityException("Unknown package " + pkg);
+ }
+ if (!UserHandle.isSameApp(ai.uid, uid)) {
+ throw new SecurityException("Calling uid " + uid + " gave package"
+ + pkg + " which is owned by uid " + ai.uid);
+ }
+ } catch (RemoteException re) {
+ throw new SecurityException("Unknown package " + pkg + "\n" + re);
+ }
+ }
+
+ private final IBinder mService = new ILiveLockScreenManagerProvider.Stub() {
+ @Override
+ public void enqueueLiveLockScreen(String pkg, int id, LiveLockScreenInfo llsInfo,
+ int[] idReceived, int userId) throws RemoteException {
+ enforceAccessPermission();
+ enforceSamePackageOrSystem(pkg, llsInfo);
+ BaseLiveLockManagerService.this.enqueueLiveLockScreen(pkg, id, llsInfo, idReceived,
+ userId);
+ }
+
+ @Override
+ public void cancelLiveLockScreen(String pkg, int id, int userId) throws RemoteException {
+ enforceAccessPermission();
+ BaseLiveLockManagerService.this.cancelLiveLockScreen(pkg, id, userId);
+ }
+
+ @Override
+ public LiveLockScreenInfo getCurrentLiveLockScreen() throws RemoteException {
+ enforceAccessPermission();
+ return BaseLiveLockManagerService.this.getCurrentLiveLockScreen();
+ }
+
+ @Override
+ public void updateDefaultLiveLockScreen(LiveLockScreenInfo llsInfo) throws RemoteException {
+ enforcePrivateAccessPermission();
+ BaseLiveLockManagerService.this.updateDefaultLiveLockScreen(llsInfo);
+ }
+
+ @Override
+ public boolean getLiveLockScreenEnabled() throws RemoteException {
+ enforceAccessPermission();
+ return BaseLiveLockManagerService.this.getLiveLockScreenEnabled();
+ }
+
+ @Override
+ public boolean registerChangeListener(
+ ILiveLockScreenChangeListener listener) throws RemoteException {
+ enforcePrivateAccessPermission();
+ return BaseLiveLockManagerService.this.registerChangeListener(listener);
+ }
+
+ @Override
+ public boolean unregisterChangeListener(
+ ILiveLockScreenChangeListener listener) throws RemoteException {
+ enforcePrivateAccessPermission();
+ return BaseLiveLockManagerService.this.unregisterChangeListener(listener);
+ }
+ };
+}
diff --git a/sdk/src/java/cyanogenmod/app/CMContextConstants.java b/sdk/src/java/cyanogenmod/app/CMContextConstants.java
new file mode 100644
index 0000000..98171b8
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/CMContextConstants.java
@@ -0,0 +1,218 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app;
+
+import android.annotation.SdkConstant;
+
+/**
+ * @hide
+ * TODO: We need to somehow make these managers accessible via getSystemService
+ */
+public final class CMContextConstants {
+
+ /**
+ * @hide
+ */
+ private CMContextConstants() {
+ // Empty constructor
+ }
+
+ /**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.app.CMStatusBarManager} for informing the user of
+ * background events.
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.app.CMStatusBarManager
+ */
+ public static final String CM_STATUS_BAR_SERVICE = "cmstatusbar";
+
+ /**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.app.ProfileManager} for informing the user of
+ * background events.
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.app.ProfileManager
+ *
+ * @hide
+ */
+ public static final String CM_PROFILE_SERVICE = "profile";
+
+ /**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.app.PartnerInterface} interact with system settings.
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.app.PartnerInterface
+ *
+ * @hide
+ */
+ public static final String CM_PARTNER_INTERFACE = "cmpartnerinterface";
+
+ /**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.app.CMTelephonyManager} to manage the phone and
+ * data connection.
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.app.CMTelephonyManager
+ *
+ * @hide
+ */
+ public static final String CM_TELEPHONY_MANAGER_SERVICE = "cmtelephonymanager";
+
+ /**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.hardware.CMHardwareManager} to manage the extended
+ * hardware features of the device.
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.hardware.CMHardwareManager
+ *
+ * @hide
+ */
+ public static final String CM_HARDWARE_SERVICE = "cmhardware";
+
+ /**
+ * @hide
+ */
+ public static final String CM_APP_SUGGEST_SERVICE = "cmappsuggest";
+
+ /**
+ * Control device power profile and characteristics.
+ *
+ * @hide
+ */
+ public static final String CM_PERFORMANCE_SERVICE = "cmperformance";
+
+ /**
+ * Controls changing and applying themes
+ *
+ * @hide
+ */
+ public static final String CM_THEME_SERVICE = "cmthemes";
+
+ /**
+ * Manages composed icons
+ *
+ * @hide
+ */
+ public static final String CM_ICON_CACHE_SERVICE = "cmiconcache";
+
+ /**
+ * @hide
+ */
+ public static final String CM_LIVE_LOCK_SCREEN_SERVICE = "cmlivelockscreen";
+
+ /**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.weather.CMWeatherManager} to manage the weather service
+ * settings and request weather updates
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.weather.CMWeatherManager
+ *
+ * @hide
+ */
+ public static final String CM_WEATHER_SERVICE = "cmweather";
+
+ /**
+ * Features supported by the CMSDK.
+ */
+ public static class Features {
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the hardware abstraction
+ * framework service utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String HARDWARE_ABSTRACTION = "org.cyanogenmod.hardware";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm status bar service
+ * utilzed by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String STATUSBAR = "org.cyanogenmod.statusbar";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm profiles service
+ * utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String PROFILES = "org.cyanogenmod.profiles";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm app suggest service
+ * utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String APP_SUGGEST = "org.cyanogenmod.appsuggest";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm telephony service
+ * utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String TELEPHONY = "org.cyanogenmod.telephony";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm theme service
+ * utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String THEMES = "org.cyanogenmod.theme";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm performance service
+ * utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String PERFORMANCE = "org.cyanogenmod.performance";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm partner service
+ * utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String PARTNER = "org.cyanogenmod.partner";
+
+ /*
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the Live lock screen
+ * feature.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String LIVE_LOCK_SCREEN = "org.cyanogenmod.livelockscreen";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm weather weather
+ * service utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String WEATHER_SERVICES = "org.cyanogenmod.weather";
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/CMStatusBarManager.java b/sdk/src/java/cyanogenmod/app/CMStatusBarManager.java
new file mode 100644
index 0000000..d696a82
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/CMStatusBarManager.java
@@ -0,0 +1,247 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import cyanogenmod.app.ICMStatusBarManager;
+
+/**
+ * The CMStatusBarManager allows you to publish and remove CustomTiles within the
+ * Quick Settings Panel.
+ *
+ * <p>
+ * Each of the publish methods takes an int id parameter and optionally a
+ * {@link String} tag parameter, which may be {@code null}. These parameters
+ * are used to form a pair (tag, id), or ({@code null}, id) if tag is
+ * unspecified. This pair identifies this custom tile from your app to the
+ * system, so that pair should be unique within your app. If you call one
+ * of the publish methods with a (tag, id) pair that is currently active and
+ * a new set of custom tile parameters, it will be updated. For example,
+ * if you pass a new custom tile icon, the old icon in the panel will
+ * be replaced with the new one. This is also the same tag and id you pass
+ * to the {@link #removeTile(int)} or {@link #removeTile(String, int)} method to clear
+ * this custom tile.
+ *
+ * <p>
+ * To get the instance of this class, utilize CMStatusBarManager#getInstance(Context context)
+ *
+ * @see cyanogenmod.app.CustomTile
+ */
+public class CMStatusBarManager {
+ private static final String TAG = "CMStatusBarManager";
+ private static boolean localLOGV = false;
+
+ private Context mContext;
+
+ private static ICMStatusBarManager sService;
+
+ private static CMStatusBarManager sCMStatusBarManagerInstance;
+ private CMStatusBarManager(Context context) {
+ Context appContext = context.getApplicationContext();
+ if (appContext != null) {
+ mContext = appContext;
+ } else {
+ mContext = context;
+ }
+ sService = getService();
+
+ if (context.getPackageManager().hasSystemFeature(
+ cyanogenmod.app.CMContextConstants.Features.STATUSBAR) && sService == null) {
+ throw new RuntimeException("Unable to get CMStatusBarService. The service either" +
+ " crashed, was not started, or the interface has been called to early in" +
+ " SystemServer init");
+ }
+ }
+
+ /**
+ * Get or create an instance of the {@link cyanogenmod.app.CMStatusBarManager}
+ * @param context
+ * @return {@link cyanogenmod.app.CMStatusBarManager}
+ */
+ public static CMStatusBarManager getInstance(Context context) {
+ if (sCMStatusBarManagerInstance == null) {
+ sCMStatusBarManagerInstance = new CMStatusBarManager(context);
+ }
+ return sCMStatusBarManagerInstance;
+ }
+
+ /**
+ * Post a custom tile to be shown in the status bar panel. If a custom tile with
+ * the same id has already been posted by your application and has not yet been removed, it
+ * will be replaced by the updated information.
+ *
+ * You will need the cyanogenmod.permission.PUBLISH_CUSTOM_TILE
+ * to utilize this functionality.
+ *
+ * @param id An identifier for this customTile unique within your
+ * application.
+ * @param customTile A {@link CustomTile} object describing what to show the user.
+ * Must not be null.
+ */
+ public void publishTile(int id, CustomTile customTile) {
+ publishTile(null, id, customTile);
+ }
+
+ /**
+ * Post a custom tile to be shown in the status bar panel. If a custom tile with
+ * the same tag and id has already been posted by your application and has not yet been
+ * removed, it will be replaced by the updated information.
+ *
+ * You will need the cyanogenmod.permission.PUBLISH_CUSTOM_TILE
+ * to utilize this functionality.
+ *
+ * @param tag A string identifier for this custom tile. May be {@code null}.
+ * @param id An identifier for this custom tile. The pair (tag, id) must be unique
+ * within your application.
+ * @param customTile A {@link cyanogenmod.app.CustomTile} object describing what to
+ * show the user. Must not be null.
+ */
+ public void publishTile(String tag, int id, CustomTile customTile) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMStatusBarManagerService");
+ return;
+ }
+
+ int[] idOut = new int[1];
+ String pkg = mContext.getPackageName();
+ if (localLOGV) Log.v(TAG, pkg + ": create(" + id + ", " + customTile + ")");
+ try {
+ sService.createCustomTileWithTag(pkg, mContext.getOpPackageName(), tag, id,
+ customTile, idOut, UserHandle.myUserId());
+ if (id != idOut[0]) {
+ Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
+ }
+ } catch (RemoteException e) {
+ Slog.w("CMStatusBarManager", "warning: no cm status bar service");
+ }
+ }
+
+ /**
+ * Similar to {@link cyanogenmod.app.CMStatusBarManager#publishTile(int id, cyanogenmod.app.CustomTile)},
+ * however lets you specify a {@link android.os.UserHandle}
+ *
+ * You will need the cyanogenmod.permission.PUBLISH_CUSTOM_TILE
+ * to utilize this functionality.
+ *
+ * @param tag A string identifier for this custom tile. May be {@code null}.
+ * @param id An identifier for this custom tile. The pair (tag, id) must be unique
+ * within your application.
+ * @param customTile A {@link cyanogenmod.app.CustomTile} object describing what to
+ * show the user. Must not be null.
+ * @param user A user handle to publish the tile as.
+ */
+ public void publishTileAsUser(String tag, int id, CustomTile customTile, UserHandle user) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMStatusBarManagerService");
+ return;
+ }
+
+ int[] idOut = new int[1];
+ String pkg = mContext.getPackageName();
+ if (localLOGV) Log.v(TAG, pkg + ": create(" + id + ", " + customTile + ")");
+ try {
+ sService.createCustomTileWithTag(pkg, mContext.getOpPackageName(), tag, id,
+ customTile, idOut, user.getIdentifier());
+ if (id != idOut[0]) {
+ Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
+ }
+ } catch (RemoteException e) {
+ Slog.w("CMStatusBarManager", "warning: no cm status bar service");
+ }
+ }
+
+ /**
+ * Remove a custom tile that's currently published to the StatusBarPanel.
+ *
+ * You will need the cyanogenmod.permission.PUBLISH_CUSTOM_TILE
+ * to utilize this functionality.
+ *
+ * @param id The identifier for the custom tile to be removed.
+ */
+ public void removeTile(int id) {
+ removeTile(null, id);
+ }
+
+ /**
+ * Remove a custom tile that's currently published to the StatusBarPanel.
+ *
+ * You will need the cyanogenmod.platform.PUBLISH_CUSTOM_TILE
+ * to utilize this functionality.
+ *
+ * @param tag The string identifier for the custom tile to be removed.
+ * @param id The identifier for the custom tile to be removed.
+ */
+ public void removeTile(String tag, int id) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMStatusBarManagerService");
+ return;
+ }
+
+ String pkg = mContext.getPackageName();
+ if (localLOGV) Log.v(TAG, pkg + ": remove(" + id + ")");
+ try {
+ sService.removeCustomTileWithTag(pkg, tag, id, UserHandle.myUserId());
+ } catch (RemoteException e) {
+ Slog.w("CMStatusBarManager", "warning: no cm status bar service");
+ }
+ }
+
+ /**
+ * Similar to {@link cyanogenmod.app.CMStatusBarManager#removeTile(String tag, int id)}
+ * however lets you specific a {@link android.os.UserHandle}
+ *
+ * You will need the cyanogenmod.platform.PUBLISH_CUSTOM_TILE
+ * to utilize this functionality.
+ *
+ * @param tag The string identifier for the custom tile to be removed.
+ * @param id The identifier for the custom tile to be removed.
+ * @param user The user handle to remove the tile from.
+ */
+ public void removeTileAsUser(String tag, int id, UserHandle user) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMStatusBarManagerService");
+ return;
+ }
+
+ String pkg = mContext.getPackageName();
+ if (localLOGV) Log.v(TAG, pkg + ": remove(" + id + ")");
+ try {
+ sService.removeCustomTileWithTag(pkg, tag, id, user.getIdentifier());
+ } catch (RemoteException e) {
+ }
+ }
+
+ /** @hide */
+ public ICMStatusBarManager getService() {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_STATUS_BAR_SERVICE);
+ if (b != null) {
+ sService = ICMStatusBarManager.Stub.asInterface(b);
+ return sService;
+ }
+ return null;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/CMTelephonyManager.java b/sdk/src/java/cyanogenmod/app/CMTelephonyManager.java
new file mode 100644
index 0000000..4285f44
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/CMTelephonyManager.java
@@ -0,0 +1,358 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.SubscriptionInfo;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.List;
+
+import cyanogenmod.app.CMContextConstants;
+
+/**
+ * The CMTelephonyManager allows you to view and manage the phone state and
+ * the data connection, with multiple SIMs support.
+ *
+ * <p>
+ * To get the instance of this class, utilize CMTelephonyManager#getInstance(Context context)
+ */
+public class CMTelephonyManager {
+
+ /**
+ * Subscription ID used to set the default Phone and SMS to "ask every time".
+ */
+ public static final int ASK_FOR_SUBSCRIPTION_ID = 0;
+
+ private static final String TAG = "CMTelephonyManager";
+ private static boolean localLOGD = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static ICMTelephonyManager sService;
+ private static CMTelephonyManager sCMTelephonyManagerInstance;
+ private Context mContext;
+
+ private CMTelephonyManager(Context context) {
+ Context appContext = context.getApplicationContext();
+ if (appContext != null) {
+ mContext = appContext;
+ } else {
+ mContext = context;
+ }
+ sService = getService();
+
+ if (context.getPackageManager().hasSystemFeature(CMContextConstants.Features.TELEPHONY)
+ && sService == null) {
+ throw new RuntimeException("Unable to get CMTelephonyManagerService. " +
+ "The service either crashed, was not started, or the interface has been " +
+ "called to early in SystemServer init");
+ }
+ }
+
+ /**
+ * Get or create an instance of the {@link cyanogenmod.app.CMTelephonyManager}
+ *
+ * @return {@link cyanogenmod.app.CMTelephonyManager}
+ */
+ public static CMTelephonyManager getInstance(Context context) {
+ if (sCMTelephonyManagerInstance == null) {
+ sCMTelephonyManagerInstance = new CMTelephonyManager(context);
+ }
+ return sCMTelephonyManagerInstance;
+ }
+
+ /** @hide */
+ public ICMTelephonyManager getService() {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_TELEPHONY_MANAGER_SERVICE);
+ if (b != null) {
+ sService = ICMTelephonyManager.Stub.asInterface(b);
+ return sService;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the list of {@link SubscriptionInfo} that are registered on the
+ * phone.
+ *
+ * @return The list of SIM subscriptions. The returning list can be null or empty.
+ * @see SubscriptionInfo
+ */
+ public List<SubscriptionInfo> getSubInformation() {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return null;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting the SIMs information");
+ }
+ List<SubscriptionInfo> subInfoList = null;
+ try {
+ subInfoList = sService.getSubInformation();
+ if (subInfoList == null) {
+ Log.w(TAG, "no subscription list was returned from the service");
+ } else if (subInfoList.isEmpty()) {
+ Log.w(TAG, "the subscription list is empty");
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+
+ return subInfoList;
+ }
+
+ /**
+ * Returns the state of the SIM by subscription ID.
+ *
+ * If the subscription ID is not valid the method will return {@code false}.
+ *
+ * @param subId The subscription ID to query.
+ * @return {@code true} if the SIM is activated (even without signal or requesting the
+ * PIN/PUK), {@code false} otherwise.
+ */
+ public boolean isSubActive(int subId) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return false;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting the state of the SIM with subscription: " + subId);
+ }
+ boolean simActive = false;
+ try {
+ simActive = sService.isSubActive(subId);
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting the SIM state with subscription " + subId + " as active: " + simActive);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+
+ return simActive;
+ }
+
+ /**
+ * Sets the state of one of the SIMs by subscription ID.
+ *
+ * If the subscription ID is not valid or the SIM already
+ * is in the desired state the method will do nothing.
+ *
+ * @param subId The subscription ID to change the state.
+ * @param state {@code true} to activate the SIM, {@code false} to disable.
+ */
+ public void setSubState(int subId, boolean state) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " setting the state of the SIM with subscription " + subId + " as active: " + state);
+ }
+
+ try {
+ sService.setSubState(subId, state);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+ }
+
+ /**
+ * Checks if the received subscription received has the data
+ * connection enabled.
+ *
+ * This method will return {@code true} (or {@code false} if inactive on the SIM)
+ * even when an internet connection is active through Wifi/BT.
+ *
+ * If the subscription ID is not valid the method will return false.
+ *
+ * @param subId The subscription ID to query.
+ * @return {@code true} if the data connection is enabled on the SIM, {@code false} otherwise.
+ */
+ public boolean isDataConnectionSelectedOnSub(int subId) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return false;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting if the data connection is enabled for SIM for subscription: " + subId);
+ }
+ boolean dataConnectionActiveOnSim = false;
+ try {
+ dataConnectionActiveOnSim = sService.isDataConnectionSelectedOnSub(subId);
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting if the data connection is enabled for SIM with subscription " +
+ subId + " as active: " + dataConnectionActiveOnSim);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+
+ return dataConnectionActiveOnSim;
+ }
+
+ /**
+ * Checks if the network data connection is enabled.
+ *
+ * This method will return {@code true} (or {@code false} if inactive)
+ * even when an internet connection is active through Wifi/BT.
+ *
+ * @return {@code true} if the network data connection is enabled, {@code false} otherwise.
+ */
+ public boolean isDataConnectionEnabled() {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return false;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting if the network data connection is enabled");
+ }
+ boolean dataConnectionEnabled = false;
+ try {
+ dataConnectionEnabled = sService.isDataConnectionEnabled();
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting if the network data connection is enabled: " + dataConnectionEnabled);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+
+ return dataConnectionEnabled;
+ }
+
+ /**
+ * Sets the network data conection active or inactive.
+ *
+ * @param state If {@code true} enables the network data connection, if {@code false} disables it.
+ */
+ public void setDataConnectionState(boolean state) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " setting the network data connection enabled: " + state);
+ }
+
+ try {
+ sService.setDataConnectionState(state);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+ }
+
+ /**
+ * Sets the data connection state on one of the SIMs by subscription ID.
+ *
+ * If the subscription ID is not valid or the data connection is already
+ * enabled on the SIM the method will do nothing.
+ *
+ * @param subId The subscription ID to set the network data connection.
+ * @hide
+ */
+ public void setDataConnectionSelectedOnSub(int subId) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " setting the network data connection for SIM with subscription: " + subId);
+ }
+
+ try {
+ sService.setDataConnectionSelectedOnSub(subId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+ }
+
+ /**
+ * Sets the default phone used to make phone calls as the one received on subId.
+ *
+ * If ASK_FOR_SUBSCRIPTION_ID is used as a parameter, then the option to choose
+ * what SIM to use is selected.
+ *
+ * @param subId The subscription to set as default for phone calls.
+ * To select SIM when calling use ASK_FOR_SUBSCRIPTION_ID.
+ */
+ public void setDefaultPhoneSub(int subId) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " setting the subscription used for phone calls as: " + subId);
+ }
+
+ try {
+ sService.setDefaultPhoneSub(subId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+ }
+
+ /**
+ * Sets the default phone used to send SMS as the one received on subId.
+ *
+ * If ASK_FOR_SUBSCRIPTION_ID is used as a parameter, then the option to choose
+ * what SIM to use is selected.
+ *
+ * @param subId The subscription to set as default for sending SMS.
+ * To select SIM when sending SMS use ASK_FOR_SUBSCRIPTION_ID.
+ */
+ public void setDefaultSmsSub(int subId) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " setting the subscription used for SMS as: " + subId);
+ }
+
+ try {
+ sService.setDefaultSmsSub(subId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/CustomTile.aidl b/sdk/src/java/cyanogenmod/app/CustomTile.aidl
new file mode 100644
index 0000000..1a35ad0
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/CustomTile.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app;
+
+parcelable CustomTile;
diff --git a/sdk/src/java/cyanogenmod/app/CustomTile.java b/sdk/src/java/cyanogenmod/app/CustomTile.java
new file mode 100644
index 0000000..dee8c37
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/CustomTile.java
@@ -0,0 +1,1106 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.RemoteViews;
+import cyanogenmod.os.Build;
+
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+import java.util.ArrayList;
+
+/**
+ * A class that represents a quick settings tile
+ *
+ * <p>The {@link cyanogenmod.app.CustomTile.Builder} has been added to make it
+ * easier to construct CustomTiles.</p>
+ */
+public class CustomTile implements Parcelable {
+
+ /** Max count allowed by PseudoGridView within SystemUi **/
+ public static final int PSEUDO_GRID_ITEM_MAX_COUNT = 9;
+
+ private String resourcesPackageName = "";
+
+ /**
+ * An optional intent to execute when the custom tile entry is clicked. If
+ * this is an activity, it must include the
+ * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
+ * that you take care of task management.
+ *
+ * This takes priority over the onClickUri.
+ **/
+ public PendingIntent onClick;
+
+ /**
+ * An optional intent to execute when the custom tile entry is long clicked. If
+ * this is an activity, it must include the
+ * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
+ * that you take care of task management. Activities will also automatically trigger
+ * the host panel to automatically collapse after executing the pending intent.
+ **/
+ public PendingIntent onLongClick;
+
+ /**
+ * An optional settings intent to execute when the custom tile's detail is shown
+ * If this is an activity, it must include the
+ * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
+ * that you take care of task management
+ */
+ public Intent onSettingsClick;
+
+ /**
+ * The intent to execute when the custom tile is explicitly removed by the user.
+ *
+ * This probably shouldn't be launching an activity since several of those will be sent
+ * at the same time.
+ */
+ public PendingIntent deleteIntent;
+
+ /**
+ * An optional Uri to be parsed and broadcast on tile click, if an onClick pending intent
+ * is specified, it will take priority over the uri to be broadcasted.
+ **/
+ public Uri onClickUri;
+
+ /**
+ * A label specific to the quick settings tile to be created
+ */
+ public String label;
+
+ /**
+ * A content description for the custom tile state
+ */
+ public String contentDescription;
+
+ /**
+ * An icon to represent the custom tile
+ */
+ public int icon;
+
+ /**
+ * A remote icon to represent the custom tile
+ */
+ public Bitmap remoteIcon;
+
+ /**
+ * An expanded style for when the CustomTile is clicked, can either be
+ * a {@link GridExpandedStyle} or a {@link ListExpandedStyle}
+ */
+ public ExpandedStyle expandedStyle;
+
+ /**
+ * Boolean that forces the status bar panel to collapse when a user clicks on the
+ * {@link CustomTile}
+ * By default {@link #collapsePanel} is true
+ */
+ public boolean collapsePanel = true;
+
+ /**
+ * Indicates whether this tile has sensitive data that have to be hidden on
+ * secure lockscreens.
+ * By default {@link #sensitiveData} is false
+ */
+ public boolean sensitiveData = false;
+
+ /**
+ * Unflatten the CustomTile from a parcel.
+ */
+ public CustomTile(Parcel parcel) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+ if (parcel.readInt() != 0) {
+ this.onClick = PendingIntent.CREATOR.createFromParcel(parcel);
+ }
+ if (parcel.readInt() != 0) {
+ this.onSettingsClick = Intent.CREATOR.createFromParcel(parcel);
+ }
+ if (parcel.readInt() != 0) {
+ this.onClickUri = Uri.CREATOR.createFromParcel(parcel);
+ }
+ if (parcel.readInt() != 0) {
+ this.label = parcel.readString();
+ }
+ if (parcel.readInt() != 0) {
+ this.contentDescription = parcel.readString();
+ }
+ if (parcel.readInt() != 0) {
+ this.expandedStyle = ExpandedStyle.CREATOR.createFromParcel(parcel);
+ }
+ this.icon = parcel.readInt();
+ }
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ this.resourcesPackageName = parcel.readString();
+ this.collapsePanel = (parcel.readInt() == 1);
+ if (parcel.readInt() != 0) {
+ this.remoteIcon = Bitmap.CREATOR.createFromParcel(parcel);
+ }
+ if (parcel.readInt() != 0) {
+ this.deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
+ }
+ this.sensitiveData = (parcel.readInt() == 1);
+ }
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.DRAGON_FRUIT) {
+ if (parcel.readInt() != 0) {
+ this.onLongClick = PendingIntent.CREATOR.createFromParcel(parcel);
+ }
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /**
+ * Constructs a CustomTile object with default values.
+ * You might want to consider using {@link cyanogenmod.app.CustomTile.Builder} instead.
+ */
+ public CustomTile()
+ {
+ // Empty constructor
+ }
+
+ /** @hide **/
+ public String getResourcesPackageName() {
+ return resourcesPackageName;
+ }
+
+ @Override
+ public CustomTile clone() {
+ CustomTile that = new CustomTile();
+ cloneInto(that);
+ return that;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ String NEW_LINE = System.getProperty("line.separator");
+ if (onClickUri != null) {
+ b.append("onClickUri=" + onClickUri.toString() + NEW_LINE);
+ }
+ if (onClick != null) {
+ b.append("onClick=" + onClick.toString() + NEW_LINE);
+ }
+ if (onLongClick != null) {
+ b.append("onLongClick=" + onLongClick.toString() + NEW_LINE);
+ }
+ if (onSettingsClick != null) {
+ b.append("onSettingsClick=" + onSettingsClick.toString() + NEW_LINE);
+ }
+ if (!TextUtils.isEmpty(label)) {
+ b.append("label=" + label + NEW_LINE);
+ }
+ if (!TextUtils.isEmpty(contentDescription)) {
+ b.append("contentDescription=" + contentDescription + NEW_LINE);
+ }
+ if (expandedStyle != null) {
+ b.append("expandedStyle=" + expandedStyle + NEW_LINE);
+ }
+
+ b.append("icon=" + icon + NEW_LINE);
+ b.append("resourcesPackageName=" + resourcesPackageName + NEW_LINE);
+ b.append("collapsePanel=" + collapsePanel + NEW_LINE);
+ if (remoteIcon != null) {
+ b.append("remoteIcon=" + remoteIcon.getGenerationId() + NEW_LINE);
+ }
+ if (deleteIntent != null) {
+ b.append("deleteIntent=" + deleteIntent.toString() + NEW_LINE);
+ }
+ b.append("sensitiveData=" + sensitiveData + NEW_LINE);
+ return b.toString();
+ }
+
+ /**
+ * Copy all of this into that
+ * @hide
+ */
+ public void cloneInto(CustomTile that) {
+ that.resourcesPackageName = this.resourcesPackageName;
+ that.onClick = this.onClick;
+ that.onLongClick = this.onLongClick;
+ that.onSettingsClick = this.onSettingsClick;
+ that.onClickUri = this.onClickUri;
+ that.label = this.label;
+ that.contentDescription = this.contentDescription;
+ that.expandedStyle = this.expandedStyle;
+ that.icon = this.icon;
+ that.collapsePanel = this.collapsePanel;
+ that.remoteIcon = this.remoteIcon;
+ that.deleteIntent = this.deleteIntent;
+ that.sensitiveData = this.sensitiveData;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(out);
+
+ // ==== APRICOT =====
+ if (onClick != null) {
+ out.writeInt(1);
+ onClick.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ if (onSettingsClick != null) {
+ out.writeInt(1);
+ onSettingsClick.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ if (onClickUri != null) {
+ out.writeInt(1);
+ onClickUri.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ if (label != null) {
+ out.writeInt(1);
+ out.writeString(label);
+ } else {
+ out.writeInt(0);
+ }
+ if (contentDescription != null) {
+ out.writeInt(1);
+ out.writeString(contentDescription);
+ } else {
+ out.writeInt(0);
+ }
+ if (expandedStyle != null) {
+ out.writeInt(1);
+ expandedStyle.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeInt(icon);
+
+ // ==== BOYSENBERRY =====
+ out.writeString(resourcesPackageName);
+ out.writeInt(collapsePanel ? 1 : 0);
+ if (remoteIcon != null) {
+ out.writeInt(1);
+ remoteIcon.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ if (deleteIntent != null) {
+ out.writeInt(1);
+ deleteIntent.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeInt(sensitiveData ? 1 : 0);
+
+ // ==== DRAGONFRUIT ====
+ if (onLongClick != null) {
+ out.writeInt(1);
+ onLongClick.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /**
+ * An object that can apply an expanded view style to a {@link CustomTile.Builder}
+ * object.
+ */
+ public static class ExpandedStyle implements Parcelable {
+ /**
+ * @hide
+ */
+ public static final int NO_STYLE = -1;
+
+ /**
+ * Identifier for a grid style expanded view
+ */
+ public static final int GRID_STYLE = 0;
+
+ /**
+ * Identifier for a list style expanded view
+ */
+ public static final int LIST_STYLE = 1;
+
+ /**
+ * Identifier for a remote view style expanded view
+ */
+ public static final int REMOTE_STYLE = 2;
+
+ private ExpandedStyle() {
+ styleId = NO_STYLE;
+ }
+
+ private RemoteViews contentViews;
+ private ExpandedItem[] expandedItems;
+ private int styleId;
+
+ private ExpandedStyle(Parcel parcel) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+ if (parcel.readInt() != 0) {
+ expandedItems = parcel.createTypedArray(ExpandedItem.CREATOR);
+ }
+ styleId = parcel.readInt();
+ }
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ if (parcel.readInt() != 0) {
+ contentViews = RemoteViews.CREATOR.createFromParcel(parcel);
+ }
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /**
+ * @hide
+ */
+ public void setBuilder(Builder builder) {
+ if (builder != null) {
+ builder.setExpandedStyle(this);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetExpandedItems(ArrayList<? extends ExpandedItem> items) {
+ if (styleId == GRID_STYLE && items.size() > PSEUDO_GRID_ITEM_MAX_COUNT) {
+ Log.w(CustomTile.class.getName(),
+ "Attempted to publish greater than max grid item count");
+ }
+ expandedItems = new ExpandedItem[items.size()];
+ items.toArray(expandedItems);
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetRemoteViews(RemoteViews remoteViews) {
+ contentViews = remoteViews;
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalStyleId(int id) {
+ styleId = id;
+ }
+
+ /**
+ * Retrieve the {@link ExpandedItem}s that have been set on this expanded style
+ * @return array of {@link ExpandedItem}
+ */
+ public ExpandedItem[] getExpandedItems() {
+ return expandedItems;
+ }
+
+ /**
+ * Retrieve the RemoteViews that have been set on this expanded style
+ * @return RemoteViews
+ */
+ public RemoteViews getContentViews() {
+ return contentViews;
+ }
+
+ /**
+ * Retrieve the style id associated with the {@link ExpandedStyle}
+ * @return id for style
+ */
+ public int getStyle() {
+ return styleId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(parcel);
+
+ // ==== APRICOT ====
+ if (expandedItems != null) {
+ parcel.writeInt(1);
+ parcel.writeTypedArray(expandedItems, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+ parcel.writeInt(styleId);
+
+ // ==== BOYSENBERRY ====
+ if (contentViews != null) {
+ parcel.writeInt(1);
+ contentViews.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ String NEW_LINE = System.getProperty("line.separator");
+ if (expandedItems != null) {
+ b.append("expandedItems= "+ NEW_LINE);
+ for (ExpandedItem item : expandedItems) {
+ b.append(" item=" + item.toString() + NEW_LINE);
+ }
+ }
+ b.append("styleId=" + styleId + NEW_LINE);
+ return b.toString();
+ }
+
+ /**
+ * Parcelable.Creator that instantiates ExpandedStyle objects
+ */
+ public static final Creator<ExpandedStyle> CREATOR =
+ new Creator<ExpandedStyle>() {
+ public ExpandedStyle createFromParcel(Parcel in) {
+ return new ExpandedStyle(in);
+ }
+
+ @Override
+ public ExpandedStyle[] newArray(int size) {
+ return new ExpandedStyle[size];
+ }
+ };
+ }
+
+ /**
+ * An instance of {@link ExpandedStyle} that shows the {@link ExpandedGridItem}s in a
+ * non-scrollable grid.
+ */
+ public static class GridExpandedStyle extends ExpandedStyle {
+ /**
+ * Constructs a GridExpandedStyle object with default values.
+ */
+ public GridExpandedStyle() {
+ internalStyleId(GRID_STYLE);
+ }
+
+ /**
+ * Sets an {@link ArrayList} of {@link ExpandedGridItem}'s to be utilized by
+ * the PseudoGridView for presentation.
+ *
+ * Since the PseudoGridView is not a Grid with an adapter instance, there's a hard
+ * limit specified by {@link #PSEUDO_GRID_ITEM_MAX_COUNT}
+ * @param expandedGridItems an array list of {@link ExpandedGridItem}s
+ */
+ public void setGridItems(ArrayList<ExpandedGridItem> expandedGridItems) {
+ internalSetExpandedItems(expandedGridItems);
+ }
+ }
+
+ /**
+ * An instance of {@link ExpandedStyle} that shows the {@link ExpandedListItem}'s in a
+ * scrollable ListView.
+ */
+ public static class ListExpandedStyle extends ExpandedStyle {
+ /**
+ * Constructs a ListExpandedStyle object with default values.
+ */
+ public ListExpandedStyle() {
+ internalStyleId(LIST_STYLE);
+ }
+
+ /**
+ * Sets an {@link ArrayList} of {@link ExpandedListItem}s to be utilized by
+ * the ListView for presentation.
+ * @param expandedListItems an array list of {@link ExpandedListItem}s
+ */
+ public void setListItems(ArrayList<ExpandedListItem> expandedListItems) {
+ internalSetExpandedItems(expandedListItems);
+ }
+ }
+
+ /**
+ * An instance of {@link ExpandedStyle} that shows a remote view in the remote process
+ */
+ public static class RemoteExpandedStyle extends ExpandedStyle {
+ /**
+ * Constructs a RemoteExpandedStyle object with default values.
+ */
+ public RemoteExpandedStyle() {
+ internalStyleId(REMOTE_STYLE);
+ }
+
+ /**
+ * Sets the RemoteViews for the {@link RemoteExpandedStyle}
+ * @param remoteViews a remote view
+ */
+ public void setRemoteViews(RemoteViews remoteViews) {
+ internalSetRemoteViews(remoteViews);
+ }
+ }
+
+ /**
+ * A container object that is utilized by {@link ExpandedStyle} to show specific items in either
+ * a PseudoGridView or a ListView via {@link GridExpandedStyle} and {@link ListExpandedStyle}
+ */
+ public static class ExpandedItem implements Parcelable {
+
+ /**
+ * A {@link PendingIntent} associated with the item.
+ * Triggers a {@link PendingIntent#send()} when the item is clicked.
+ */
+ public PendingIntent onClickPendingIntent;
+
+ /**
+ * A drawable resource id associated with the {@link ExpandedItem}
+ */
+ public int itemDrawableResourceId;
+
+ /**
+ * A bitmap to be utilized instead of #itemDrawableResourceId
+ */
+ public Bitmap itemBitmapResource;
+
+ /**
+ * The title of the item
+ */
+ public String itemTitle;
+
+ /**
+ * The summary associated with the item, may be null
+ */
+ public String itemSummary = null;
+
+ private ExpandedItem() {
+ // Don't want to have this baseclass be instantiable
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetItemDrawable(int resourceId) {
+ itemDrawableResourceId = resourceId;
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetItemBitmap(Bitmap bitmap) {
+ itemBitmapResource = bitmap;
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetItemSummary(String resourceId) {
+ itemSummary = resourceId;
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetItemTitle(String title) {
+ itemTitle = title;
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetOnClickPendingIntent(PendingIntent pendingIntent) {
+ onClickPendingIntent = pendingIntent;
+ }
+
+ /**
+ * Unflatten the ExpandedItem from a parcel.
+ */
+ protected ExpandedItem(Parcel parcel) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+ if (parcel.readInt() != 0) {
+ onClickPendingIntent = PendingIntent.CREATOR.createFromParcel(parcel);
+ }
+ if (parcel.readInt() != 0) {
+ itemTitle = parcel.readString();
+ }
+ if (parcel.readInt() != 0) {
+ itemSummary = parcel.readString();
+ }
+ itemDrawableResourceId = parcel.readInt();
+ }
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ if (parcel.readInt() != 0) {
+ itemBitmapResource = Bitmap.CREATOR.createFromParcel(parcel);
+ }
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(out);
+
+ // ==== APRICOT ====
+ if (onClickPendingIntent != null) {
+ out.writeInt(1);
+ onClickPendingIntent.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ if (!TextUtils.isEmpty(itemTitle)) {
+ out.writeInt(1);
+ out.writeString(itemTitle);
+ } else {
+ out.writeInt(0);
+ }
+ if (!TextUtils.isEmpty(itemSummary)) {
+ out.writeInt(1);
+ out.writeString(itemSummary);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeInt(itemDrawableResourceId);
+
+ // ==== BOYSENBERRY ====
+ if (itemBitmapResource != null) {
+ out.writeInt(1);
+ itemBitmapResource.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ String NEW_LINE = System.getProperty("line.separator");
+ if (onClickPendingIntent != null) {
+ b.append("onClickPendingIntent= " + onClickPendingIntent.toString() + NEW_LINE);
+ }
+ if (itemTitle != null) {
+ b.append("itemTitle= " + itemTitle.toString() + NEW_LINE);
+ }
+ if (itemSummary != null) {
+ b.append("itemSummary= " + itemSummary.toString() + NEW_LINE);
+ }
+ b.append("itemDrawableResourceId=" + itemDrawableResourceId + NEW_LINE);
+ if (itemBitmapResource != null) {
+ b.append("itemBitmapResource=" + itemBitmapResource.getGenerationId() + NEW_LINE);
+ }
+ return b.toString();
+ }
+
+ public static final Creator<ExpandedItem> CREATOR =
+ new Creator<ExpandedItem>() {
+ @Override
+ public ExpandedItem createFromParcel(Parcel in) {
+ return new ExpandedItem(in);
+ }
+
+ @Override
+ public ExpandedItem[] newArray(int size) {
+ return new ExpandedItem[size];
+ }
+ };
+ }
+
+ /**
+ * An instance of {@link ExpandedItem} to be utilized within a {@link GridExpandedStyle}
+ */
+ public static class ExpandedGridItem extends ExpandedItem {
+ /**
+ * Constructor for the ExpandedGridItem
+ */
+ public ExpandedGridItem() {
+ }
+
+ /**
+ * Sets the title for the {@link ExpandedGridItem}
+ * @param title a string title
+ */
+ public void setExpandedGridItemTitle(String title) {
+ internalSetItemTitle(title);
+ }
+
+ /**
+ * Sets the {@link PendingIntent} associated with the {@link ExpandedGridItem}
+ * @param intent a pending intent to be triggered on click
+ */
+ public void setExpandedGridItemOnClickIntent(PendingIntent intent) {
+ internalSetOnClickPendingIntent(intent);
+ }
+
+ /**
+ * Sets the drawable resource id associated with the {@link ExpandedGridItem}
+ * @param resourceId a resource id that maps to a drawable
+ */
+ public void setExpandedGridItemDrawable(int resourceId) {
+ internalSetItemDrawable(resourceId);
+ }
+
+ /**
+ * Sets the bitmap associated with the {@link ExpandedGridItem} to be utilized instead of
+ * the {@link ExpandedItem#itemDrawableResourceId}
+ *
+ * Note, sending many items with bitmaps over IPC may result in a
+ * TransactionTooLargeException.
+ * @param bitmap
+ */
+ public void setExpandedGridItemBitmap(Bitmap bitmap) {
+ internalSetItemBitmap(bitmap);
+ }
+ }
+
+ /**
+ * An instance of {@link ExpandedItem} to be utilized within a {@link ListExpandedStyle}
+ */
+ public static class ExpandedListItem extends ExpandedItem {
+ /**
+ * Constructor fot the ExpandedListItem
+ */
+ public ExpandedListItem() {
+ }
+
+ /**
+ * Sets the title for the {@link ExpandedListItem}
+ * @param title a string title
+ */
+ public void setExpandedListItemTitle(String title) {
+ internalSetItemTitle(title);
+ }
+
+ /**
+ * Sets the title for the {@link ExpandedListItem}
+ * @param summary a string summary
+ */
+ public void setExpandedListItemSummary(String summary) {
+ internalSetItemSummary(summary);
+ }
+
+ /**
+ * Sets the {@link PendingIntent} associated with the {@link ExpandedListItem}
+ * @param intent a pending intent to be triggered on click
+ */
+ public void setExpandedListItemOnClickIntent(PendingIntent intent) {
+ internalSetOnClickPendingIntent(intent);
+ }
+
+ /**
+ * Sets the drawable resource id associated with the {@link ExpandedListItem}
+ * @param resourceId a resource id that maps to a drawable
+ */
+ public void setExpandedListItemDrawable(int resourceId) {
+ internalSetItemDrawable(resourceId);
+ }
+
+ /**
+ * Sets the bitmap associated with the {@link ExpandedListItem} to be utilized instead of
+ * the {@link ExpandedItem#itemDrawableResourceId}
+ *
+ * Note, sending many items with bitmaps over IPC may result in a
+ * TransactionTooLargeException.
+ * @param bitmap
+ */
+ public void setExpandedListItemBitmap(Bitmap bitmap) {
+ internalSetItemBitmap(bitmap);
+ }
+ }
+
+ /**
+ * Parcelable.Creator that instantiates CustomTile objects
+ */
+ public static final Creator<CustomTile> CREATOR =
+ new Creator<CustomTile>() {
+ public CustomTile createFromParcel(Parcel in) {
+ return new CustomTile(in);
+ }
+
+ @Override
+ public CustomTile[] newArray(int size) {
+ return new CustomTile[size];
+ }
+ };
+
+ /**
+ * Builder class for {@link cyanogenmod.app.CustomTile} objects.
+ *
+ * Provides a convenient way to set the various fields of a {@link cyanogenmod.app.CustomTile}
+ *
+ * <p>Example:
+ *
+ * <pre class="prettyprint">
+ * CustomTile customTile = new CustomTile.Builder(mContext)
+ * .setLabel("custom label")
+ * .setContentDescription("custom description")
+ * .setOnClickIntent(pendingIntent)
+ * .setOnSettingsClickIntent(intent)
+ * .setOnClickUri(Uri.parse("custom uri"))
+ * .setIcon(R.drawable.ic_launcher)
+ * .build();
+ * </pre>
+ */
+ public static class Builder {
+ private PendingIntent mOnClick;
+ private PendingIntent mOnLongClick;
+ private Intent mOnSettingsClick;
+ private Uri mOnClickUri;
+ private String mLabel;
+ private String mContentDescription;
+ private int mIcon;
+ private Bitmap mRemoteIcon;
+ private Context mContext;
+ private ExpandedStyle mExpandedStyle;
+ private boolean mCollapsePanel = true;
+ private PendingIntent mDeleteIntent;
+ private boolean mSensitiveData = false;
+
+ /**
+ * Constructs a new Builder with the defaults:
+ */
+ public Builder(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Set the label for the custom tile
+ * @param label a string to be used for the custom tile label
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setLabel(String label) {
+ mLabel = label;
+ return this;
+ }
+
+ /**
+ * Set the label for the custom tile
+ * @param id a string resource id to be used for the custom tile label
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setLabel(int id) {
+ mLabel = mContext.getString(id);
+ return this;
+ }
+
+ /**
+ * Set the content description for the custom tile
+ * @param contentDescription a string to explain content
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setContentDescription(String contentDescription) {
+ mContentDescription = contentDescription;
+ return this;
+ }
+
+ /**
+ * Set the content description for the custom tile
+ * @param id a string resource id to explain content
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setContentDescription(int id) {
+ mContentDescription = mContext.getString(id);
+ return this;
+ }
+
+ /**
+ * Set a {@link android.app.PendingIntent} to be fired on custom tile click
+ * @param intent
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setOnClickIntent(PendingIntent intent) {
+ mOnClick = intent;
+ return this;
+ }
+
+ /**
+ * Set a {@link android.app.PendingIntent} to be fired on custom tile long press.
+ * Note: if this is an activity, the host panel will automatically collapse.
+ * @param intent
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setOnLongClickIntent(PendingIntent intent) {
+ mOnLongClick = intent;
+ return this;
+ }
+
+ /**
+ * Set a settings {@link android.content.Intent} to be fired on custom
+ * tile detail pane click
+ * @param intent
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setOnSettingsClickIntent(Intent intent) {
+ mOnSettingsClick = intent;
+ return this;
+ }
+
+ /**
+ * Set a {@link android.net.Uri} to be broadcasted in an intent on custom tile click
+ * @param uri
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setOnClickUri(Uri uri) {
+ mOnClickUri = uri;
+ return this;
+ }
+
+ /**
+ * Set an icon for the custom tile to be presented to the user
+ *
+ * @param drawableId
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setIcon(int drawableId) {
+ mIcon = drawableId;
+ return this;
+ }
+
+ /**
+ * Set a bitmap icon to the custom tile to be utilized instead of {@link CustomTile#icon}
+ *
+ * This will unset {@link #setIcon(int)} if utilized together.
+ * @see CustomTile#remoteIcon
+ * @param remoteIcon
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setIcon(Bitmap remoteIcon) {
+ mIcon = 0; // empty
+ mRemoteIcon = remoteIcon;
+ return this;
+ }
+
+ /**
+ * Set an {@link ExpandedStyle} to to be displayed when a user clicks the custom tile
+ * @param expandedStyle
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setExpandedStyle(ExpandedStyle expandedStyle) {
+ if (mExpandedStyle != expandedStyle) {
+ mExpandedStyle = expandedStyle;
+ if (mExpandedStyle != null) {
+ expandedStyle.setBuilder(this);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Set whether or not the Statusbar Panel should be collapsed when an
+ * {@link #onClick} or {@link #onClickUri} event is fired.
+ * @param bool
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder shouldCollapsePanel(boolean bool) {
+ mCollapsePanel = bool;
+ return this;
+ }
+
+ /**
+ * Supply a {@link PendingIntent} to send when the custom tile is cleared explicitly
+ * by the user.
+ *
+ * @see CustomTile#deleteIntent
+ * @param intent
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setDeleteIntent(PendingIntent intent) {
+ mDeleteIntent = intent;
+ return this;
+ }
+
+ /**
+ * Indicates whether this tile has sensitive data that have to be hidden
+ * on secure lockscreens.
+ * @param bool
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder hasSensitiveData(boolean bool) {
+ mSensitiveData = bool;
+ return this;
+ }
+
+ /**
+ * Create a {@link cyanogenmod.app.CustomTile} object
+ * @return {@link cyanogenmod.app.CustomTile}
+ */
+ public CustomTile build() {
+ CustomTile tile = new CustomTile();
+ tile.resourcesPackageName = mContext.getPackageName();
+ tile.onClick = mOnClick;
+ tile.onLongClick = mOnLongClick;
+ tile.onSettingsClick = mOnSettingsClick;
+ tile.onClickUri = mOnClickUri;
+ tile.label = mLabel;
+ tile.contentDescription = mContentDescription;
+ tile.expandedStyle = mExpandedStyle;
+ tile.icon = mIcon;
+ tile.collapsePanel = mCollapsePanel;
+ tile.remoteIcon = mRemoteIcon;
+ tile.deleteIntent = mDeleteIntent;
+ tile.sensitiveData = mSensitiveData;
+ return tile;
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/CustomTileListenerService.java b/sdk/src/java/cyanogenmod/app/CustomTileListenerService.java
new file mode 100644
index 0000000..2c8036f
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/CustomTileListenerService.java
@@ -0,0 +1,232 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app;
+
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import cyanogenmod.app.ICustomTileListener;
+import cyanogenmod.app.ICMStatusBarManager;
+
+import org.cyanogenmod.internal.statusbar.IStatusBarCustomTileHolder;
+
+/**
+ * A service that receives calls from the system when new custom tiles are
+ * posted or removed.
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the cyanogenmod.permission.BIND_CUSTOM_TILE_LISTENER_SERVICE
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * &lt;service android:name=".CustomTileListener"
+ * android:label="&#64;string/service_name"
+ * android:permission="cyanogenmod.permission.BIND_CUSTOM_TILE_LISTENER_SERVICE">
+ * &lt;intent-filter>
+ * &lt;action android:name="cyanogenmod.app.CustomTileListenerService" />
+ * &lt;/intent-filter>
+ * &lt;/service></pre>
+ */
+public class CustomTileListenerService extends Service {
+ private final String TAG = CustomTileListenerService.class.getSimpleName()
+ + "[" + getClass().getSimpleName() + "]";
+ /**
+ * The {@link android.content.Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE
+ = "cyanogenmod.app.CustomTileListenerService";
+
+ private ICustomTileListenerWrapper mWrapper = null;
+ private ICMStatusBarManager mStatusBarService;
+ /** Only valid after a successful call to (@link registerAsService}. */
+ private int mCurrentUser;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (mWrapper == null) {
+ mWrapper = new ICustomTileListenerWrapper();
+ }
+ return mWrapper;
+ }
+
+ private final ICMStatusBarManager getStatusBarInterface() {
+ if (mStatusBarService == null) {
+ mStatusBarService = ICMStatusBarManager.Stub.asInterface(
+ ServiceManager.getService(CMContextConstants.CM_STATUS_BAR_SERVICE));
+ }
+ return mStatusBarService;
+ }
+
+ /**
+ * Directly register this service with the StatusBar Manager.
+ *
+ * <p>Only system services may use this call. It will fail for non-system callers.
+ * Apps should ask the user to add their listener in Settings.
+ *
+ * @param context Context required for accessing resources. Since this service isn't
+ * launched as a real Service when using this method, a context has to be passed in.
+ * @param componentName the component that will consume the custom tile information
+ * @param currentUser the user to use as the stream filter
+ * @hide
+ */
+ public void registerAsSystemService(Context context, ComponentName componentName,
+ int currentUser) throws RemoteException {
+ if (isBound()) {
+ return;
+ }
+ ICMStatusBarManager statusBarInterface = mStatusBarService;
+ if (mStatusBarService != null) {
+ mWrapper = new ICustomTileListenerWrapper();
+ statusBarInterface.registerListener(mWrapper, componentName, currentUser);
+ mCurrentUser = currentUser;
+ }
+ }
+
+ /**
+ * Directly unregister this service from the StatusBar Manager.
+ *
+ * <P>This method will fail for listeners that were not registered
+ * with (@link registerAsService).
+ * @hide
+ */
+ public void unregisterAsSystemService() throws RemoteException {
+ if (isBound()) {
+ ICMStatusBarManager statusBarInterface = mStatusBarService;
+ statusBarInterface.unregisterListener(mWrapper, mCurrentUser);
+ mWrapper = null;
+ mStatusBarService = null;
+ }
+ }
+
+
+ private class ICustomTileListenerWrapper extends ICustomTileListener.Stub {
+ @Override
+ public void onListenerConnected() {
+ synchronized (mWrapper) {
+ try {
+ CustomTileListenerService.this.onListenerConnected();
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onListenerConnected", t);
+ }
+ }
+ }
+ @Override
+ public void onCustomTilePosted(IStatusBarCustomTileHolder sbcHolder) {
+ StatusBarPanelCustomTile sbc;
+ try {
+ sbc = sbcHolder.get();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onCustomTilePosted: Error receiving StatusBarPanelCustomTile", e);
+ return;
+ }
+ synchronized (mWrapper) {
+ try {
+ CustomTileListenerService.this.onCustomTilePosted(sbc);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onCustomTilePosted", t);
+ }
+ }
+ }
+ @Override
+ public void onCustomTileRemoved(IStatusBarCustomTileHolder sbcHolder) {
+ StatusBarPanelCustomTile sbc;
+ try {
+ sbc = sbcHolder.get();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onCustomTileRemoved: Error receiving StatusBarPanelCustomTile", e);
+ return;
+ }
+ synchronized (mWrapper) {
+ try {
+ CustomTileListenerService.this.onCustomTileRemoved(sbc);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onCustomTileRemoved", t);
+ }
+ }
+ }
+ }
+
+ /**
+ * Implement this method to learn about new custom tiles as they are posted by apps.
+ *
+ * @param sbc A data structure encapsulating the original {@link cyanogenmod.app.CustomTile}
+ * object as well as its identifying information (tag and id) and source
+ * (package name).
+ */
+ public void onCustomTilePosted(StatusBarPanelCustomTile sbc) {
+ // optional
+ }
+
+ /**
+ * Implement this method to learn when custom tiles are removed.
+ *
+ * @param sbc A data structure encapsulating at least the original information (tag and id)
+ * and source (package name) used to post the {@link cyanogenmod.app.CustomTile} that
+ * was just removed.
+ */
+ public void onCustomTileRemoved(StatusBarPanelCustomTile sbc) {
+ // optional
+ }
+
+ /**
+ * Implement this method to learn about when the listener is enabled and connected to
+ * the status bar manager.
+ * at this time.
+ */
+ public void onListenerConnected() {
+ // optional
+ }
+
+ /**
+ * Inform the {@link cyanogenmod.app.CMStatusBarManager} about dismissal of a single custom tile.
+ * <p>
+ * Use this if your listener has a user interface that allows the user to dismiss individual
+ * custom tiles, similar to the behavior of Android's status bar and notification panel.
+ * It should be called after the user dismisses a single custom tile using your UI;
+ * upon being informed, the cmstatusbar manager will actually remove the custom tile
+ * and you will get an {@link #onCustomTileRemoved(StatusBarPanelCustomTile)} callback.
+ * <P>
+ *
+ * @param pkg Package of the notifying app.
+ * @param tag Tag of the custom tile as specified by the notifying app
+ * @param id ID of the custom tile as specified by the notifying app
+ * <p>
+ */
+ public final void removeCustomTile(String pkg, String tag, int id) {
+ if (!isBound()) return;
+ try {
+ mStatusBarService.removeCustomTileFromListener(
+ mWrapper, pkg, tag, id);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact cmstautusbar manager", ex);
+ }
+ }
+
+ private boolean isBound() {
+ if (getStatusBarInterface() == null || mWrapper == null) {
+ Log.w(TAG, "CustomTile listener service not yet bound.");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/ICMStatusBarManager.aidl b/sdk/src/java/cyanogenmod/app/ICMStatusBarManager.aidl
new file mode 100644
index 0000000..62283a5
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ICMStatusBarManager.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app;
+
+import android.content.ComponentName;
+
+import cyanogenmod.app.CustomTile;
+import cyanogenmod.app.ICustomTileListener;
+
+/** @hide */
+interface ICMStatusBarManager {
+ // --- Methods below are for use by 3rd party applications to publish quick
+ // settings tiles to the status bar panel
+ // You need the PUBLISH_CUSTOM_TILE permission
+ void createCustomTileWithTag(String pkg, String opPkg, String tag, int id,
+ in CustomTile tile, inout int[] idReceived, int userId);
+ void removeCustomTileWithTag(String pkg, String tag, int id, int userId);
+
+ // --- Methods below are for use by 3rd party applications
+ // You need the BIND_QUICK_SETTINGS_TILE_LISTENER permission
+ void registerListener(in ICustomTileListener listener, in ComponentName component, int userid);
+ void unregisterListener(in ICustomTileListener listener, int userid);
+ void removeCustomTileFromListener(in ICustomTileListener listener, String pkg, String tag, int id);
+}
diff --git a/sdk/src/java/cyanogenmod/app/ICMTelephonyManager.aidl b/sdk/src/java/cyanogenmod/app/ICMTelephonyManager.aidl
new file mode 100644
index 0000000..743d61c
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ICMTelephonyManager.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app;
+
+import android.telephony.SubscriptionInfo;
+
+import java.util.List;
+
+/** @hide */
+interface ICMTelephonyManager {
+ // --- Methods below are for use by 3rd party applications to manage phone and data connection
+ // You need the READ_MSIM_PHONE_STATE permission
+ List<SubscriptionInfo> getSubInformation();
+ boolean isSubActive(int subId);
+ boolean isDataConnectionSelectedOnSub(int subId);
+ boolean isDataConnectionEnabled();
+
+ // You need the MODIFY_MSIM_PHONE_STATE permission
+ void setSubState(int subId, boolean state);
+ void setDataConnectionSelectedOnSub(int subId);
+ void setDataConnectionState(boolean state);
+ void setDefaultPhoneSub(int subId);
+ void setDefaultSmsSub(int subId);
+}
diff --git a/sdk/src/java/cyanogenmod/app/ICustomTileListener.aidl b/sdk/src/java/cyanogenmod/app/ICustomTileListener.aidl
new file mode 100644
index 0000000..9f21f52
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ICustomTileListener.aidl
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app;
+
+import cyanogenmod.app.StatusBarPanelCustomTile;
+
+import org.cyanogenmod.internal.statusbar.IStatusBarCustomTileHolder;
+
+/** @hide */
+oneway interface ICustomTileListener
+{
+ void onListenerConnected();
+ void onCustomTilePosted(in IStatusBarCustomTileHolder customTileHolder);
+ void onCustomTileRemoved(in IStatusBarCustomTileHolder customTileHolder);
+}
diff --git a/sdk/src/java/cyanogenmod/app/ILiveLockScreenChangeListener.aidl b/sdk/src/java/cyanogenmod/app/ILiveLockScreenChangeListener.aidl
new file mode 100644
index 0000000..48e7f36
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ILiveLockScreenChangeListener.aidl
@@ -0,0 +1,27 @@
+/*
+** 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 cyanogenmod.app;
+
+import cyanogenmod.app.LiveLockScreenInfo;
+
+/**
+ * Listener interface for notifying clients that the current Live lock screen has changed.
+ * @hide
+ */
+interface ILiveLockScreenChangeListener {
+ void onLiveLockScreenChanged(in LiveLockScreenInfo llsInfo);
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/app/ILiveLockScreenManager.aidl b/sdk/src/java/cyanogenmod/app/ILiveLockScreenManager.aidl
new file mode 100644
index 0000000..15142c1
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ILiveLockScreenManager.aidl
@@ -0,0 +1,73 @@
+/*
+** 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 cyanogenmod.app;
+
+import cyanogenmod.app.ILiveLockScreenChangeListener;
+import cyanogenmod.app.LiveLockScreenInfo;
+
+/** @hide */
+interface ILiveLockScreenManager {
+
+ /**
+ * Enqueue a Live lock screen to be displayed.
+ */
+ void enqueueLiveLockScreen(String pkg, int id, in LiveLockScreenInfo lls,
+ inout int[] idReceived, int userId);
+
+ /**
+ * Cancel displaying a Live lock screen.
+ */
+ void cancelLiveLockScreen(String pkg, int id, int userId);
+
+ /**
+ * Get the current Live lock screen that should be displayed.
+ */
+ LiveLockScreenInfo getCurrentLiveLockScreen();
+
+ /**
+ * Get the default Live lock screen. This is the Live lock screen that should be displayed
+ * when no other Live lock screens are queued.
+ */
+ LiveLockScreenInfo getDefaultLiveLockScreen();
+
+ /**
+ * Set the default Live lock screen. This is the Live lock screen that should be displayed
+ * when no other Live lock screens are queued.
+ */
+ void setDefaultLiveLockScreen(in LiveLockScreenInfo llsInfo);
+
+ /**
+ * Set whether Live lock screen feature is enabled.
+ */
+ oneway void setLiveLockScreenEnabled(boolean enabled);
+
+ /**
+ * Get the enabled state of the Live lock screen feature.
+ */
+ boolean getLiveLockScreenEnabled();
+
+ /**
+ * Registers an ILiveLockScreenChangeListener that will be called when the current Live lock
+ * screen changes.
+ */
+ boolean registerChangeListener(in ILiveLockScreenChangeListener listener);
+
+ /**
+ * Unregisters a previously registered ILiveLockScreenChangeListener.
+ */
+ boolean unregisterChangeListener(in ILiveLockScreenChangeListener listener);
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/app/ILiveLockScreenManagerProvider.aidl b/sdk/src/java/cyanogenmod/app/ILiveLockScreenManagerProvider.aidl
new file mode 100644
index 0000000..933eb97
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ILiveLockScreenManagerProvider.aidl
@@ -0,0 +1,65 @@
+/*
+** 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 cyanogenmod.app;
+
+import cyanogenmod.app.ILiveLockScreenChangeListener;
+import cyanogenmod.app.LiveLockScreenInfo;
+
+/**
+ * Interface to be implemented by services that support the
+ * {@link LiveLockScreenManager#SERVICE_INTERFACE}
+ * @hide
+ */
+interface ILiveLockScreenManagerProvider {
+
+ /**
+ * Enqueue a Live lock screen to be displayed.
+ */
+ void enqueueLiveLockScreen(String pkg, int id, in LiveLockScreenInfo lls,
+ inout int[] idReceived, int userId);
+
+ /**
+ * Cancel displaying a Live lock screen.
+ */
+ void cancelLiveLockScreen(String pkg, int id, int userId);
+
+ /**
+ * Get the current Live lock screen that should be displayed.
+ */
+ LiveLockScreenInfo getCurrentLiveLockScreen();
+
+ /**
+ * Called when the default Live lock screen has changed.
+ */
+ oneway void updateDefaultLiveLockScreen(in LiveLockScreenInfo llsInfo);
+
+ /**
+ * Get the enabled state of the Live lock screen feature.
+ */
+ boolean getLiveLockScreenEnabled();
+
+ /**
+ * Registers an ILiveLockScreenChangeListener that will be called when the current Live lock
+ * screen changes.
+ */
+ boolean registerChangeListener(in ILiveLockScreenChangeListener listener);
+
+ /**
+ * Unregisters a previously registered ILiveLockScreenChangeListener.
+ */
+ boolean unregisterChangeListener(in ILiveLockScreenChangeListener listener);
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/app/IPartnerInterface.aidl b/sdk/src/java/cyanogenmod/app/IPartnerInterface.aidl
new file mode 100644
index 0000000..1b02dd1
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/IPartnerInterface.aidl
@@ -0,0 +1,30 @@
+/* //device/java/android/android/app/IProfileManager.aidl
+**
+** Copyright (C) 2015 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 cyanogenmod.app;
+
+/** {@hide} */
+interface IPartnerInterface
+{
+ void setAirplaneModeEnabled(boolean enabled);
+ void setMobileDataEnabled(boolean enabled);
+ boolean setZenMode(int mode);
+ void shutdown();
+ void reboot();
+ String getCurrentHotwordPackageName();
+ boolean setZenModeWithDuration(int mode, long durationMillis);
+}
diff --git a/sdk/src/java/cyanogenmod/app/IProfileManager.aidl b/sdk/src/java/cyanogenmod/app/IProfileManager.aidl
new file mode 100644
index 0000000..091ba55
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/IProfileManager.aidl
@@ -0,0 +1,51 @@
+/* //device/java/android/android/app/IProfileManager.aidl
+**
+** Copyright (C) 2015 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 cyanogenmod.app;
+
+import cyanogenmod.app.Profile;
+import android.app.NotificationGroup;
+import android.os.ParcelUuid;
+
+/** {@hide} */
+interface IProfileManager
+{
+ boolean setActiveProfile(in ParcelUuid profileParcelUuid);
+ boolean setActiveProfileByName(String profileName);
+ Profile getActiveProfile();
+
+ boolean addProfile(in Profile profile);
+ boolean removeProfile(in Profile profile);
+ void updateProfile(in Profile profile);
+
+ Profile getProfile(in ParcelUuid profileParcelUuid);
+ Profile getProfileByName(String profileName);
+ Profile[] getProfiles();
+ boolean profileExists(in ParcelUuid profileUuid);
+ boolean profileExistsByName(String profileName);
+ boolean notificationGroupExistsByName(String notificationGroupName);
+
+ NotificationGroup[] getNotificationGroups();
+ void addNotificationGroup(in NotificationGroup group);
+ void removeNotificationGroup(in NotificationGroup group);
+ void updateNotificationGroup(in NotificationGroup group);
+ NotificationGroup getNotificationGroupForPackage(in String pkg);
+ NotificationGroup getNotificationGroup(in ParcelUuid groupParcelUuid);
+
+ void resetAll();
+ boolean isEnabled();
+}
diff --git a/sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.aidl b/sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.aidl
new file mode 100644
index 0000000..bffa3b0
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 cyanogenmod.app;
+
+parcelable LiveLockScreenInfo;
diff --git a/sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.java b/sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.java
new file mode 100644
index 0000000..5ac2220
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.java
@@ -0,0 +1,189 @@
+/*
+ * 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 cyanogenmod.app;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import android.text.TextUtils;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+/**
+ * Data structure defining a Live lock screen.
+ */
+public class LiveLockScreenInfo implements Parcelable {
+
+ /**
+ * Default Live lock screen {@link #priority}.
+ */
+ public static final int PRIORITY_DEFAULT = 0;
+
+ /**
+ * Lower {@link #priority}, for items that are less important.
+ */
+ public static final int PRIORITY_LOW = -1;
+
+ /**
+ * Lowest {@link #priority}.
+ */
+ public static final int PRIORITY_MIN = -2;
+
+ /**
+ * Higher {@link #priority}, for items that are more important
+ */
+ public static final int PRIORITY_HIGH = 1;
+
+ /**
+ * Highest {@link #priority}.
+ */
+ public static final int PRIORITY_MAX = 2;
+
+ /**
+ * The component, which implements
+ * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService}, to display for this
+ * live lock screen.
+ */
+ public ComponentName component;
+
+ /**
+ * Relative priority for this Live lock screen.
+ */
+ public int priority;
+
+ /**
+ * Constructs a LiveLockScreenInfo object with the given values.
+ * You might want to consider using {@link Builder} instead.
+ */
+ public LiveLockScreenInfo(@NonNull ComponentName component, int priority) {
+ this.component = component;
+ this.priority = priority;
+ }
+
+ /**
+ * Constructs a LiveLockScreenInfo object with default values.
+ * You might want to consider using {@link Builder} instead.
+ */
+ public LiveLockScreenInfo()
+ {
+ this.component = null;
+ this.priority = PRIORITY_DEFAULT;
+ }
+
+ private LiveLockScreenInfo(Parcel source) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(source);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ this.priority = source.readInt();
+ String component = source.readString();
+ this.component = !TextUtils.isEmpty(component)
+ ? ComponentName.unflattenFromString(component)
+ : null;
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ dest.writeInt(priority);
+ dest.writeString(component != null ? component.flattenToString() : "");
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public String toString() {
+ return "LiveLockScreenInfo: priority=" + priority +
+ ", component=" + component;
+ }
+
+ @Override
+ public LiveLockScreenInfo clone() {
+ LiveLockScreenInfo that = new LiveLockScreenInfo();
+ cloneInto(that);
+ return that;
+ }
+
+ /**
+ * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
+ * of this into that.
+ * @hide
+ */
+ public void cloneInto(LiveLockScreenInfo that) {
+ that.component = this.component.clone();
+ that.priority = this.priority;
+ }
+
+ public static final Parcelable.Creator<LiveLockScreenInfo> CREATOR =
+ new Parcelable.Creator<LiveLockScreenInfo>() {
+ @Override
+ public LiveLockScreenInfo createFromParcel(Parcel source) {
+ return new LiveLockScreenInfo(source);
+ }
+
+ @Override
+ public LiveLockScreenInfo[] newArray(int size) {
+ return new LiveLockScreenInfo[0];
+ }
+ };
+
+ /**
+ * Builder class for {@link LiveLockScreenInfo} objects. Provides a convenient way to set
+ * various fields of a {@link LiveLockScreenInfo}.
+ */
+ public static class Builder {
+ private int mPriority;
+ private ComponentName mComponent;
+
+ public Builder setPriority(int priority) {
+ if (priority < PRIORITY_MIN || priority > PRIORITY_MAX) {
+ throw new IllegalArgumentException("Invalid priorty given (" + priority + "): " +
+ PRIORITY_MIN + " <= priority <= " + PRIORITY_MIN);
+ }
+ mPriority = priority;
+ return this;
+ }
+
+ public Builder setComponent(@NonNull ComponentName component) {
+ if (component == null) {
+ throw new IllegalArgumentException(
+ "Cannot call setComponent with a null component");
+ }
+ mComponent = component;
+ return this;
+ }
+
+ public LiveLockScreenInfo build() {
+ return new LiveLockScreenInfo(mComponent, mPriority);
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/LiveLockScreenManager.java b/sdk/src/java/cyanogenmod/app/LiveLockScreenManager.java
new file mode 100644
index 0000000..c5fa4ce
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/LiveLockScreenManager.java
@@ -0,0 +1,182 @@
+/*
+ * 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 cyanogenmod.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+
+/**
+ * Manages enabling/disabling Live lock screens as well as what Live lock screen to display when
+ * enabled.
+ */
+public class LiveLockScreenManager {
+ private static final String TAG = LiveLockScreenManager.class.getSimpleName();
+ private static ILiveLockScreenManager sService;
+ private static LiveLockScreenManager sInstance;
+
+ private Context mContext;
+
+ /**
+ * The {@link android.content.Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE
+ = "cyanogenmod.app.LiveLockScreenManagerService";
+
+ private LiveLockScreenManager(Context context) {
+ mContext = context;
+ sService = getService();
+ if (context.getPackageManager().hasSystemFeature(
+ CMContextConstants.Features.LIVE_LOCK_SCREEN) && sService == null) {
+ throw new RuntimeException("Unable to get LiveLockScreenManagerService. " +
+ "The service either crashed, was not started, or the interface has " +
+ "been called to early in SystemServer init");
+ }
+ }
+
+ private ILiveLockScreenManager getService() {
+ if (sService == null) {
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_LIVE_LOCK_SCREEN_SERVICE);
+ if (b != null) {
+ sService = ILiveLockScreenManager.Stub.asInterface(b);
+ }
+ }
+
+ return sService;
+ }
+
+ private void logServiceException(Exception e) {
+ Log.w(TAG, "Unable to access LiveLockScreenServiceBroker", e);
+ }
+
+ public static LiveLockScreenManager getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new LiveLockScreenManager(context);
+ }
+
+ return sInstance;
+ }
+
+ /**
+ * Requests a Live lock screen, defined in {@param lls}, to be displayed with the given id.
+ * @param id An identifier for this notification unique within your application.
+ * @param llsInfo A {@link LiveLockScreenInfo} object describing what Live lock screen to show
+ * the user.
+ * @return True if the Live lock screen was successfully received by the backing service
+ */
+ public boolean show(int id, @NonNull final LiveLockScreenInfo llsInfo) {
+ int[] idOut = new int[1];
+ String pkg = mContext.getPackageName();
+ boolean success = true;
+ try {
+ sService.enqueueLiveLockScreen(pkg, id, llsInfo, idOut, UserHandle.myUserId());
+ if (id != idOut[0]) {
+ Log.w(TAG, "show: id corrupted: sent " + id + ", got back " + idOut[0]);
+ success = false;
+ }
+ } catch (RemoteException e) {
+ logServiceException(e);
+ success = false;
+ }
+
+ return success;
+ }
+
+ /**
+ * Cancels a previously shown Live lock screen.
+ * @param id An identifier for this notification unique within your application.
+ */
+ public void cancel(int id) {
+ String pkg = mContext.getPackageName();
+ try {
+ sService.cancelLiveLockScreen(pkg, id, UserHandle.myUserId());
+ } catch (RemoteException e) {
+ logServiceException(e);
+ }
+ }
+
+ /**
+ * Sets the default Live lock screen to display when no other Live lock screens are queued
+ * up for display.
+ * <p>
+ * This is not available to third party applications.
+ * </p>
+ */
+ public void setDefaultLiveLockScreen(@Nullable LiveLockScreenInfo llsInfo) {
+ try {
+ sService.setDefaultLiveLockScreen(llsInfo);
+ } catch (RemoteException e) {
+ logServiceException(e);
+ }
+ }
+
+ /**
+ * Gets the default Live lock screen that is displayed when no other Live lock screens are
+ * queued up for display.
+ * <p>
+ * This is not available to third party applications.
+ * </p>
+ */
+ public LiveLockScreenInfo getDefaultLiveLockScreen() {
+ try {
+ return sService.getDefaultLiveLockScreen();
+ } catch (RemoteException e) {
+ logServiceException(e);
+ }
+
+ return null;
+ }
+
+ /** @hide */
+ public LiveLockScreenInfo getCurrentLiveLockScreen() {
+ LiveLockScreenInfo lls = null;
+ try {
+ lls = sService.getCurrentLiveLockScreen();
+ } catch (RemoteException e) {
+ logServiceException(e);
+ }
+
+ return lls;
+ }
+
+ /** @hide */
+ public boolean getLiveLockScreenEnabled() {
+ try {
+ return sService.getLiveLockScreenEnabled();
+ } catch (RemoteException e) {
+ logServiceException(e);
+ }
+
+ return false;
+ }
+
+ /** @hide */
+ public void setLiveLockScreenEnabled(boolean enabled) {
+ try {
+ sService.setLiveLockScreenEnabled(enabled);
+ } catch (RemoteException e) {
+ logServiceException(e);
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/PartnerInterface.java b/sdk/src/java/cyanogenmod/app/PartnerInterface.java
new file mode 100644
index 0000000..a7661ff
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/PartnerInterface.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2015 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 cyanogenmod.app;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * <p>
+ * The PartnerInterface allows applications to interact with a subset of system settings.
+ * </p>
+ */
+public class PartnerInterface {
+ /**
+ * Turn off zen mode. This restores the original ring and interruption
+ * settings that the user had set before zen mode was enabled.
+ *
+ * @see #setZenMode
+ */
+ public static final int ZEN_MODE_OFF = 0;
+ /**
+ * Sets zen mode to important interruptions mode, so that
+ * only notifications that the user has chosen in Settings
+ * to be of high importance will cause the user's device to notify them.
+ *
+ * This condition is held indefinitely until changed again.
+ *
+ * @see #setZenMode and #setZenModeWithDuration
+ */
+ public static final int ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1;
+ /**
+ * Sets zen mode so that no interruptions will be allowed. The device is
+ * effectively silenced indefinitely, until the mode is changed again.
+ *
+ * @see #setZenMode and #setZenModeWithDuration
+ */
+ public static final int ZEN_MODE_NO_INTERRUPTIONS = 2;
+
+
+ private static IPartnerInterface sService;
+
+ private Context mContext;
+
+ /**
+ * Allows an application to change network settings,
+ * such as enabling or disabling airplane mode.
+ */
+ public static final String MODIFY_NETWORK_SETTINGS_PERMISSION =
+ "cyanogenmod.permission.MODIFY_NETWORK_SETTINGS";
+
+ /**
+ * Allows an application to change system sound settings, such as the zen mode.
+ */
+ public static final String MODIFY_SOUND_SETTINGS_PERMISSION =
+ "cyanogenmod.permission.MODIFY_SOUND_SETTINGS";
+
+ private static final String TAG = "PartnerInterface";
+
+ private static PartnerInterface sPartnerInterfaceInstance;
+
+ private PartnerInterface(Context context) {
+ Context appContext = context.getApplicationContext();
+ if (appContext != null) {
+ mContext = appContext;
+ } else {
+ mContext = context;
+ }
+ sService = getService();
+ if (context.getPackageManager().hasSystemFeature(
+ CMContextConstants.Features.PARTNER) && sService == null) {
+ throw new RuntimeException("Unable to get PartnerInterfaceService. The service" +
+ " either crashed, was not started, or the interface has been called to early" +
+ " in SystemServer init");
+ }
+ }
+
+ /**
+ * Get or create an instance of the {@link cyanogenmod.app.PartnerInterface}
+ * @param context
+ * @return {@link PartnerInterface}
+ */
+ public static PartnerInterface getInstance(Context context) {
+ if (sPartnerInterfaceInstance == null) {
+ sPartnerInterfaceInstance = new PartnerInterface(context);
+ }
+ return sPartnerInterfaceInstance;
+ }
+
+ /** @hide */
+ static public IPartnerInterface getService() {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_PARTNER_INTERFACE);
+ if (b != null) {
+ sService = IPartnerInterface.Stub.asInterface(b);
+ return sService;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Turns on or off airplane mode.
+ *
+ * You will need {@link #MODIFY_NETWORK_SETTINGS_PERMISSION}
+ * to utilize this functionality.
+ * @param enabled if true, sets airplane mode to enabled, otherwise disables airplane mode.
+ */
+ public void setAirplaneModeEnabled(boolean enabled) {
+ if (sService == null) {
+ return;
+ }
+ try {
+ sService.setAirplaneModeEnabled(enabled);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Turns on or off mobile network.
+ *
+ * You will need {@link #MODIFY_NETWORK_SETTINGS_PERMISSION}
+ * to utilize this functionality.
+ * @param enabled if true, sets mobile network to enabled, otherwise disables mobile network.
+ */
+ public void setMobileDataEnabled(boolean enabled) {
+ if (sService == null) {
+ return;
+ }
+ try {
+ sService.setMobileDataEnabled(enabled);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Set the zen mode for the device.
+ *
+ * You will need {@link #MODIFY_SOUND_SETTINGS_PERMISSION}
+ * to utilize this functionality.
+ *
+ * @see #ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ * @see #ZEN_MODE_NO_INTERRUPTIONS
+ * @see #ZEN_MODE_OFF
+ * @param mode The zen mode to set the device to.
+ * One of {@link #ZEN_MODE_IMPORTANT_INTERRUPTIONS},
+ * {@link #ZEN_MODE_NO_INTERRUPTIONS} or
+ * {@link #ZEN_MODE_OFF}.
+ */
+ public boolean setZenMode(int mode) {
+ if (sService == null) {
+ return false;
+ }
+ try {
+ return sService.setZenMode(mode);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return false;
+ }
+
+ /**
+ * Set the zen mode for the device, allow a duration parameter
+ *
+ * You will need {@link #MODIFY_SOUND_SETTINGS_PERMISSION}
+ * to utilize this functionality.
+ *
+ * @see #ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ * @see #ZEN_MODE_NO_INTERRUPTIONS
+ * @param mode The zen mode to set the device to.
+ * One of {@link #ZEN_MODE_IMPORTANT_INTERRUPTIONS},
+ * {@link #ZEN_MODE_NO_INTERRUPTIONS}.
+ * If using {@link #ZEN_MODE_OFF}, the duration will be ignored
+ * @param durationMillis The duration in milliseconds,
+ * if duration + System.currentTimeMillis() results in
+ * long overflow, duration will be "indefinitely".
+ * all durationMillis < 0 will mean "indefinitely".
+ *
+ */
+ public boolean setZenModeWithDuration(int mode, long durationMillis) {
+ if (sService == null) {
+ return false;
+ }
+ try {
+ return sService.setZenModeWithDuration(mode, durationMillis);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return false;
+ }
+
+ /**
+ * Shuts down the device, immediately.
+ *
+ * You will need {@link android.Manifest.permission.REBOOT}
+ * to utilize this functionality.
+ */
+ public void shutdownDevice() {
+ if (sService == null) {
+ return;
+ }
+ try {
+ sService.shutdown();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Reboots the device, immediately.
+ *
+ * You will need {@link android.Manifest.permission.REBOOT}
+ * to utilize this functionality.
+ */
+ public void rebootDevice() {
+ if (sService == null) {
+ return;
+ }
+ try {
+ sService.reboot();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Retrieves the package name of the application that currently holds the
+ * {@link cyanogenmod.media.MediaRecorder.AudioSource#HOTWORD} input.
+ * @return The package name or null if no application currently holds the HOTWORD input.
+ */
+ public String getCurrentHotwordPackageName() {
+ if (sService == null) {
+ return null;
+ }
+ try {
+ return sService.getCurrentHotwordPackageName();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/Profile.aidl b/sdk/src/java/cyanogenmod/app/Profile.aidl
new file mode 100644
index 0000000..ff6c54e
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/Profile.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (C) 2015 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 cyanogenmod.app;
+
+parcelable Profile;
diff --git a/sdk/src/java/cyanogenmod/app/Profile.java b/sdk/src/java/cyanogenmod/app/Profile.java
new file mode 100755
index 0000000..9a4666d
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/Profile.java
@@ -0,0 +1,1365 @@
+/*
+ * Copyright (C) 2015 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 cyanogenmod.app;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.policy.IKeyguardService;
+import cyanogenmod.os.Build;
+import cyanogenmod.profiles.AirplaneModeSettings;
+import cyanogenmod.profiles.BrightnessSettings;
+import cyanogenmod.profiles.ConnectionSettings;
+import cyanogenmod.profiles.LockSettings;
+import cyanogenmod.profiles.RingModeSettings;
+import cyanogenmod.profiles.StreamSettings;
+
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+
+/**
+ * A class that represents a device profile.
+ *
+ * A {@link Profile} can serve a multitude of purposes, allowing the creator(user)
+ * to set overrides for streams, triggers, screen lock, brightness, various other
+ * settings.
+ */
+public final class Profile implements Parcelable, Comparable {
+
+ private String mName;
+
+ private int mNameResId;
+
+ private UUID mUuid;
+
+ private ArrayList<UUID> mSecondaryUuids = new ArrayList<UUID>();
+
+ private Map<UUID, ProfileGroup> profileGroups = new HashMap<UUID, ProfileGroup>();
+
+ private ProfileGroup mDefaultGroup;
+
+ private boolean mStatusBarIndicator = false;
+
+ private boolean mDirty;
+
+ private static final String TAG = "Profile";
+
+ private int mProfileType;
+
+ private Map<Integer, StreamSettings> streams = new HashMap<Integer, StreamSettings>();
+
+ private Map<String, ProfileTrigger> mTriggers = new HashMap<String, ProfileTrigger>();
+
+ private Map<Integer, ConnectionSettings> connections = new HashMap<Integer, ConnectionSettings>();
+
+ private Map<Integer, ConnectionSettings> networkConnectionSubIds = new HashMap<>();
+
+ private RingModeSettings mRingMode = new RingModeSettings();
+
+ private AirplaneModeSettings mAirplaneMode = new AirplaneModeSettings();
+
+ private BrightnessSettings mBrightness = new BrightnessSettings();
+
+ private LockSettings mScreenLockMode = new LockSettings();
+
+ private int mExpandedDesktopMode = ExpandedDesktopMode.DEFAULT;
+
+ private int mDozeMode = DozeMode.DEFAULT;
+
+ private int mNotificationLightMode = NotificationLightMode.DEFAULT;
+
+ /**
+ * Lock modes of a device
+ */
+ public static class LockMode {
+ /** Represents a default state lock mode (user choice) */
+ public static final int DEFAULT = 0;
+ /** Represents an insecure state lock mode, where the device has no security screen */
+ public static final int INSECURE = 1;
+ /** Represents a disabled state lock mode, where the devices lock screen can be removed */
+ public static final int DISABLE = 2;
+ }
+
+ /**
+ * Expanded desktop modes available on a device
+ */
+ public static class ExpandedDesktopMode {
+ /** Represents a default state expanded desktop mode (user choice) */
+ public static final int DEFAULT = 0;
+ /** Represents an enabled expanded desktop mode */
+ public static final int ENABLE = 1;
+ /** Represents a disabled expanded desktop mode */
+ public static final int DISABLE = 2;
+ }
+
+ /**
+ * Doze modes available on a device
+ */
+ public static class DozeMode {
+ /** Represents a default Doze mode (user choice) */
+ public static final int DEFAULT = 0;
+ /** Represents an enabled Doze mode */
+ public static final int ENABLE = 1;
+ /** Represents an disabled Doze mode */
+ public static final int DISABLE = 2;
+ }
+
+ /**
+ * Notification light modes available on a device
+ */
+ public static class NotificationLightMode {
+ /** Represents a default Notification light mode (user choice) */
+ public static final int DEFAULT = 0;
+ /** Represents an enabled Notification light mode */
+ public static final int ENABLE = 1;
+ /** Represents a disabled Notification light mode */
+ public static final int DISABLE = 2;
+ }
+
+ /**
+ * Available trigger types on the device, usually hardware
+ */
+ public static class TriggerType {
+ /** Represents a WiFi trigger type */
+ public static final int WIFI = 0;
+ /** Represents a Bluetooth trigger type */
+ public static final int BLUETOOTH = 1;
+ }
+
+ /**
+ * Various trigger states associated with a {@link TriggerType}
+ */
+ public static class TriggerState {
+ /** A {@link TriggerState) for when the {@link TriggerType} connects */
+ public static final int ON_CONNECT = 0;
+ /** A {@link TriggerState) for when the {@link TriggerType} disconnects */
+ public static final int ON_DISCONNECT = 1;
+ /** A {@link TriggerState) for when the {@link TriggerType} is disabled */
+ public static final int DISABLED = 2;
+ /**
+ * A {@link TriggerState) for when the {@link TriggerType#BLUETOOTH}
+ * connects for A2DP session
+ */
+ public static final int ON_A2DP_CONNECT = 3;
+ /**
+ * A {@link TriggerState) for when the {@link TriggerType#BLUETOOTH}
+ * disconnects from A2DP session
+ */
+ public static final int ON_A2DP_DISCONNECT = 4;
+ }
+
+ /**
+ * A {@link Profile} type
+ */
+ public static class Type {
+ /** Profile type which represents a toggle {@link Profile} */
+ public static final int TOGGLE = 0;
+ /** Profile type which represents a conditional {@link Profile} */
+ public static final int CONDITIONAL = 1;
+ }
+
+ /**
+ * A {@link ProfileTrigger} is a {@link TriggerType} which can be queried from the OS
+ */
+ public static class ProfileTrigger implements Parcelable {
+ private int mType;
+ private String mId;
+ private String mName;
+ private int mState;
+
+
+ /**
+ * Construct a {@link ProfileTrigger} based on its type {@link Profile.TriggerType} and if
+ * the trigger should fire on a {@link Profile.TriggerState} change.
+ *
+ * Example:
+ * <pre class="prettyprint">
+ * triggerId = trigger.getSSID(); // Use the AP's SSID as identifier
+ * triggerName = trigger.getTitle(); // Use the AP's name as the trigger name
+ * triggerType = Profile.TriggerType.WIFI; // This is a wifi trigger
+ * triggerState = Profile.TriggerState.ON_CONNECT; // On Connect of this, trigger
+ *
+ * Profile.ProfileTrigger profileTrigger =
+ * new Profile.ProfileTrigger(triggerType, triggerId, triggerState, triggerName);
+ * </pre>
+ *
+ * @param type a {@link Profile.TriggerType}
+ * @param id an identifier for the ProfileTrigger
+ * @param state {@link Profile.TriggerState} depending on the TriggerType
+ * @param name an identifying name for the ProfileTrigger
+ */
+ public ProfileTrigger(int type, String id, int state, String name) {
+ mType = type;
+ mId = id;
+ mState = state;
+ mName = name;
+ }
+
+ private ProfileTrigger(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ mType = in.readInt();
+ mId = in.readString();
+ mState = in.readInt();
+ mName = in.readString();
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ dest.writeInt(mType);
+ dest.writeString(mId);
+ dest.writeInt(mState);
+ dest.writeString(mName);
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Get the {@link ProfileTrigger} {@link TriggerType}
+ * @return {@link TriggerType}
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Get the name associated with the {@link ProfileTrigger}
+ * @return a string name
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Get the id associated with the {@link ProfileTrigger}
+ * @return an string identifier
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Get the state associated with the {@link ProfileTrigger}
+ * @return an integer indicating the state
+ */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * @hide
+ */
+ public void getXmlString(StringBuilder builder, Context context) {
+ final String itemType = mType == TriggerType.WIFI ? "wifiAP" : "btDevice";
+
+ builder.append("<");
+ builder.append(itemType);
+ builder.append(" ");
+ builder.append(getIdType(mType));
+ builder.append("=\"");
+ builder.append(mId);
+ builder.append("\" state=\"");
+ builder.append(mState);
+ builder.append("\" name=\"");
+ builder.append(mName);
+ builder.append("\"></");
+ builder.append(itemType);
+ builder.append(">\n");
+ }
+
+ /**
+ * @hide
+ */
+ public static ProfileTrigger fromXml(XmlPullParser xpp, Context context) {
+ final String name = xpp.getName();
+ final int type;
+
+ if (name.equals("wifiAP")) {
+ type = TriggerType.WIFI;
+ } else if (name.equals("btDevice")) {
+ type = TriggerType.BLUETOOTH;
+ } else {
+ return null;
+ }
+
+ String id = xpp.getAttributeValue(null, getIdType(type));
+ int state = Integer.valueOf(xpp.getAttributeValue(null, "state"));
+ String triggerName = xpp.getAttributeValue(null, "name");
+ if (triggerName == null) {
+ triggerName = id;
+ }
+
+ return new ProfileTrigger(type, id, state, triggerName);
+ }
+
+ private static String getIdType(int type) {
+ return type == TriggerType.WIFI ? "ssid" : "address";
+ }
+
+ /**
+ * @hide
+ */
+ public static final Parcelable.Creator<ProfileTrigger> CREATOR
+ = new Parcelable.Creator<ProfileTrigger>() {
+ public ProfileTrigger createFromParcel(Parcel in) {
+ return new ProfileTrigger(in);
+ }
+
+ @Override
+ public ProfileTrigger[] newArray(int size) {
+ return new ProfileTrigger[size];
+ }
+ };
+ }
+
+ /** @hide */
+ public static final Parcelable.Creator<Profile> CREATOR = new Parcelable.Creator<Profile>() {
+ public Profile createFromParcel(Parcel in) {
+ return new Profile(in);
+ }
+
+ @Override
+ public Profile[] newArray(int size) {
+ return new Profile[size];
+ }
+ };
+
+ public Profile(String name) {
+ this(name, -1, UUID.randomUUID());
+ }
+
+ /** @hide */
+ public Profile(String name, int nameResId, UUID uuid) {
+ mName = name;
+ mNameResId = nameResId;
+ mUuid = uuid;
+ mProfileType = Type.TOGGLE; //Default to toggle type
+ mDirty = false;
+ }
+
+ private Profile(Parcel in) {
+ readFromParcel(in);
+ }
+
+ /**
+ * Get the {@link TriggerState} for a {@link ProfileTrigger} with a given id
+ * @param type {@link TriggerType}
+ * @param id string id of {@link ProfileTrigger}
+ * @return {@link TriggerState}
+ */
+ public int getTriggerState(int type, String id) {
+ ProfileTrigger trigger = id != null ? mTriggers.get(id) : null;
+ if (trigger != null) {
+ return trigger.mState;
+ }
+ return TriggerState.DISABLED;
+ }
+
+ /**
+ * Get all the {@link ProfileTrigger}s for a given {@link TriggerType}
+ * @param type {@link TriggerType}
+ * @return an array list of {@link ProfileTrigger}s
+ */
+ public ArrayList<ProfileTrigger> getTriggersFromType(int type) {
+ ArrayList<ProfileTrigger> result = new ArrayList<ProfileTrigger>();
+ for (Entry<String, ProfileTrigger> profileTrigger: mTriggers.entrySet()) {
+ ProfileTrigger trigger = profileTrigger.getValue();
+ if (trigger.getType() == type) {
+ result.add(trigger);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Set a custom {@link ProfileTrigger}
+ * @hide
+ */
+ public void setTrigger(int type, String id, int state, String name) {
+ if (id == null
+ || type < TriggerType.WIFI || type > TriggerType.BLUETOOTH
+ || state < TriggerState.ON_CONNECT || state > TriggerState.ON_A2DP_DISCONNECT) {
+ return;
+ }
+
+ ProfileTrigger trigger = mTriggers.get(id);
+
+ if (state == TriggerState.DISABLED) {
+ if (trigger != null) {
+ mTriggers.remove(id);
+ }
+ } else if (trigger != null) {
+ trigger.mState = state;
+ } else {
+ mTriggers.put(id, new ProfileTrigger(type, id, state, name));
+ }
+
+ mDirty = true;
+ }
+
+ /**
+ * Set a {@link ProfileTrigger} on the {@link Profile}
+ * @param trigger a {@link ProfileTrigger}
+ */
+ public void setTrigger(ProfileTrigger trigger) {
+ setTrigger(trigger.getType(), trigger.getId(), trigger.getState(), trigger.getName());
+ }
+
+ public int compareTo(Object obj) {
+ Profile tmp = (Profile) obj;
+ if (mName.compareTo(tmp.mName) < 0) {
+ return -1;
+ } else if (mName.compareTo(tmp.mName) > 0) {
+ return 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Add a {@link ProfileGroup} to the {@link Profile}
+ * @param profileGroup
+ * @hide
+ */
+ public void addProfileGroup(ProfileGroup profileGroup) {
+ if (profileGroup == null) {
+ return;
+ }
+
+ if (profileGroup.isDefaultGroup()) {
+ /* we must not have more than one default group */
+ if (mDefaultGroup != null) {
+ return;
+ }
+ mDefaultGroup = profileGroup;
+ }
+ profileGroups.put(profileGroup.getUuid(), profileGroup);
+ mDirty = true;
+ }
+
+ /**
+ * Remove a {@link ProfileGroup} with a given {@link UUID}
+ * @param uuid
+ * @hide
+ */
+ public void removeProfileGroup(UUID uuid) {
+ if (!profileGroups.get(uuid).isDefaultGroup()) {
+ profileGroups.remove(uuid);
+ } else {
+ Log.e(TAG, "Cannot remove default group: " + uuid);
+ }
+ }
+
+ /**
+ * Get {@link ProfileGroup}s associated with the {@link Profile}
+ * @return {@link ProfileGroup[]}
+ * @hide
+ */
+ public ProfileGroup[] getProfileGroups() {
+ return profileGroups.values().toArray(new ProfileGroup[profileGroups.size()]);
+ }
+
+ /**
+ * Get a {@link ProfileGroup} with a given {@link UUID}
+ * @param uuid
+ * @return a {@link ProfileGroup}
+ * @hide
+ */
+ public ProfileGroup getProfileGroup(UUID uuid) {
+ return profileGroups.get(uuid);
+ }
+
+ /**
+ * Get the default {@link ProfileGroup} associated with the {@link Profile}
+ * @return the default {@link ProfileGroup}
+ * @hide
+ */
+ public ProfileGroup getDefaultGroup() {
+ return mDefaultGroup;
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // === BOYSENBERRY ===
+ if (!TextUtils.isEmpty(mName)) {
+ dest.writeInt(1);
+ dest.writeString(mName);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mNameResId != 0) {
+ dest.writeInt(1);
+ dest.writeInt(mNameResId);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mUuid != null) {
+ dest.writeInt(1);
+ new ParcelUuid(mUuid).writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mSecondaryUuids != null && !mSecondaryUuids.isEmpty()) {
+ ArrayList<ParcelUuid> uuids = new ArrayList<ParcelUuid>(mSecondaryUuids.size());
+ for (UUID u : mSecondaryUuids) {
+ uuids.add(new ParcelUuid(u));
+ }
+ dest.writeInt(1);
+ dest.writeParcelableArray(uuids.toArray(new Parcelable[uuids.size()]), flags);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mStatusBarIndicator ? 1 : 0);
+ dest.writeInt(mProfileType);
+ dest.writeInt(mDirty ? 1 : 0);
+ if (profileGroups != null && !profileGroups.isEmpty()) {
+ dest.writeInt(1);
+ dest.writeTypedArray(profileGroups.values().toArray(
+ new ProfileGroup[0]), flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (streams != null && !streams.isEmpty()) {
+ dest.writeInt(1);
+ dest.writeTypedArray(streams.values().toArray(
+ new StreamSettings[0]), flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (connections != null && !connections.isEmpty()) {
+ dest.writeInt(1);
+ dest.writeTypedArray(connections.values().toArray(
+ new ConnectionSettings[0]), flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mRingMode != null) {
+ dest.writeInt(1);
+ mRingMode.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mAirplaneMode != null) {
+ dest.writeInt(1);
+ mAirplaneMode.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mBrightness != null) {
+ dest.writeInt(1);
+ mBrightness.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mScreenLockMode != null) {
+ dest.writeInt(1);
+ mScreenLockMode.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeTypedArray(mTriggers.values().toArray(new ProfileTrigger[0]), flags);
+ dest.writeInt(mExpandedDesktopMode);
+ dest.writeInt(mDozeMode);
+
+ // === ELDERBERRY ===
+ dest.writeInt(mNotificationLightMode);
+
+ if (networkConnectionSubIds != null && !networkConnectionSubIds.isEmpty()) {
+ dest.writeInt(1);
+ dest.writeTypedArray(networkConnectionSubIds.values().toArray(
+ new ConnectionSettings[0]), flags);
+ } else {
+ dest.writeInt(0);
+ }
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /** @hide */
+ public void readFromParcel(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ if (in.readInt() != 0) {
+ mName = in.readString();
+ }
+ if (in.readInt() != 0) {
+ mNameResId = in.readInt();
+ }
+ if (in.readInt() != 0) {
+ mUuid = ParcelUuid.CREATOR.createFromParcel(in).getUuid();
+ }
+ if (in.readInt() != 0) {
+ for (Parcelable parcel : in.readParcelableArray(null)) {
+ ParcelUuid u = (ParcelUuid) parcel;
+ mSecondaryUuids.add(u.getUuid());
+ }
+ }
+ mStatusBarIndicator = (in.readInt() == 1);
+ mProfileType = in.readInt();
+ mDirty = (in.readInt() == 1);
+ if (in.readInt() != 0) {
+ for (ProfileGroup group : in.createTypedArray(ProfileGroup.CREATOR)) {
+ profileGroups.put(group.getUuid(), group);
+ if (group.isDefaultGroup()) {
+ mDefaultGroup = group;
+ }
+ }
+ }
+ if (in.readInt() != 0) {
+ for (StreamSettings stream : in.createTypedArray(StreamSettings.CREATOR)) {
+ streams.put(stream.getStreamId(), stream);
+ }
+ }
+ if (in.readInt() != 0) {
+ for (ConnectionSettings connection :
+ in.createTypedArray(ConnectionSettings.CREATOR)) {
+ connections.put(connection.getConnectionId(), connection);
+ }
+ }
+ if (in.readInt() != 0) {
+ mRingMode = RingModeSettings.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
+ mAirplaneMode = AirplaneModeSettings.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
+ mBrightness = BrightnessSettings.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
+ mScreenLockMode = LockSettings.CREATOR.createFromParcel(in);
+ }
+ for (ProfileTrigger trigger : in.createTypedArray(ProfileTrigger.CREATOR)) {
+ mTriggers.put(trigger.mId, trigger);
+ }
+ mExpandedDesktopMode = in.readInt();
+ mDozeMode = in.readInt();
+ }
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ mNotificationLightMode = in.readInt();
+ if (in.readInt() != 0) {
+ for (ConnectionSettings connection :
+ in.createTypedArray(ConnectionSettings.CREATOR)) {
+ // elderberry can do msim connections
+ networkConnectionSubIds.put(connection.getSubId(), connection);
+ }
+ }
+ }
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /**
+ * Get the name associated with the {@link Profile}
+ * @return a string name of the profile
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Set a name for the {@link Profile}
+ * @param name a string for the {@link Profile}
+ */
+ public void setName(String name) {
+ mName = name;
+ mNameResId = -1;
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link Type} of the {@link Profile}
+ * @return
+ */
+ public int getProfileType() {
+ return mProfileType;
+ }
+
+ /**
+ * Set the {@link Type} for the {@link Profile}
+ * @param type a type of profile
+ */
+ public void setProfileType(int type) {
+ mProfileType = type;
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link UUID} associated with the {@link Profile}
+ * @return the uuid for the profile
+ */
+ public UUID getUuid() {
+ if (this.mUuid == null) this.mUuid = UUID.randomUUID();
+ return this.mUuid;
+ }
+
+ /**
+ * Get the secondary {@link UUID}s for the {@link Profile}
+ * @return the secondary uuids for the Profile
+ */
+ public UUID[] getSecondaryUuids() {
+ return mSecondaryUuids.toArray(new UUID[mSecondaryUuids.size()]);
+ }
+
+ /**
+ * Set a list of secondary {@link UUID}s for the {@link Profile}
+ * @param uuids
+ */
+ public void setSecondaryUuids(List<UUID> uuids) {
+ mSecondaryUuids.clear();
+ if (uuids != null) {
+ mSecondaryUuids.addAll(uuids);
+ mDirty = true;
+ }
+ }
+
+ /**
+ * Add a secondary {@link UUID} to the {@link Profile}
+ * @param uuid
+ */
+ public void addSecondaryUuid(UUID uuid) {
+ if (uuid != null) {
+ mSecondaryUuids.add(uuid);
+ mDirty = true;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean getStatusBarIndicator() {
+ return mStatusBarIndicator;
+ }
+
+ /**
+ * @hide
+ */
+ public void setStatusBarIndicator(boolean newStatusBarIndicator) {
+ mStatusBarIndicator = newStatusBarIndicator;
+ mDirty = true;
+ }
+
+ /**
+ * Check if the given {@link Profile} is a {@link Type#CONDITIONAL}
+ * @return true if conditional
+ */
+ public boolean isConditionalType() {
+ return(mProfileType == Type.CONDITIONAL ? true : false);
+ }
+
+ /**
+ * @hide
+ */
+ public void setConditionalType() {
+ mProfileType = Type.CONDITIONAL;
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link RingModeSettings} for the {@link Profile}
+ * @return
+ */
+ public RingModeSettings getRingMode() {
+ return mRingMode;
+ }
+
+ /**
+ * Set the {@link RingModeSettings} for the {@link Profile}
+ * @param descriptor
+ */
+ public void setRingMode(RingModeSettings descriptor) {
+ mRingMode = descriptor;
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link LockSettings} for the {@link Profile}
+ * @return
+ */
+ public LockSettings getScreenLockMode() {
+ return mScreenLockMode;
+ }
+
+ /**
+ * Set the {@link LockSettings} for the {@link Profile}
+ * @param screenLockMode
+ */
+ public void setScreenLockMode(LockSettings screenLockMode) {
+ mScreenLockMode = screenLockMode;
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link ExpandedDesktopMode} for the {@link Profile}
+ * @return
+ */
+ public int getExpandedDesktopMode() {
+ return mExpandedDesktopMode;
+ }
+
+ /**
+ * Set the {@link ExpandedDesktopMode} for the {@link Profile}
+ * @return
+ */
+ public void setExpandedDesktopMode(int expandedDesktopMode) {
+ if (expandedDesktopMode < ExpandedDesktopMode.DEFAULT
+ || expandedDesktopMode > ExpandedDesktopMode.DISABLE) {
+ mExpandedDesktopMode = ExpandedDesktopMode.DEFAULT;
+ } else {
+ mExpandedDesktopMode = expandedDesktopMode;
+ }
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link DozeMode} associated with the {@link Profile}
+ * @return
+ */
+ public int getDozeMode() {
+ return mDozeMode;
+ }
+
+ /**
+ * Set the {@link DozeMode} associated with the {@link Profile}
+ * @return
+ */
+ public void setDozeMode(int dozeMode) {
+ if (dozeMode < DozeMode.DEFAULT
+ || dozeMode > DozeMode.DISABLE) {
+ mDozeMode = DozeMode.DEFAULT;
+ } else {
+ mDozeMode = dozeMode;
+ }
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link NotificationLightMode} associated with the {@link Profile}
+ * @return
+ */
+ public int getNotificationLightMode() {
+ return mNotificationLightMode;
+ }
+
+ /**
+ * Set the {@link NotificationLightMode} associated with the {@link Profile}
+ * @return
+ */
+ public void setNotificationLightMode(int notificationLightMode) {
+ if (notificationLightMode < NotificationLightMode.DEFAULT
+ || notificationLightMode > NotificationLightMode.DISABLE) {
+ mNotificationLightMode = NotificationLightMode.DEFAULT;
+ } else {
+ mNotificationLightMode = notificationLightMode;
+ }
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link AirplaneModeSettings} associated with the {@link Profile}
+ * @return
+ */
+ public AirplaneModeSettings getAirplaneMode() {
+ return mAirplaneMode;
+ }
+
+ /**
+ * Set the {@link AirplaneModeSettings} associated with the {@link Profile}
+ * @param descriptor
+ */
+ public void setAirplaneMode(AirplaneModeSettings descriptor) {
+ mAirplaneMode = descriptor;
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link BrightnessSettings} associated with the {@link Profile}
+ * @return
+ */
+ public BrightnessSettings getBrightness() {
+ return mBrightness;
+ }
+
+ /**
+ * Set the {@link BrightnessSettings} associated with the {@link Profile}
+ * @return
+ */
+ public void setBrightness(BrightnessSettings descriptor) {
+ mBrightness = descriptor;
+ mDirty = true;
+ }
+
+ /** @hide */
+ public boolean isDirty() {
+ if (mDirty) {
+ return true;
+ }
+ for (ProfileGroup group : profileGroups.values()) {
+ if (group.isDirty()) {
+ return true;
+ }
+ }
+ for (StreamSettings stream : streams.values()) {
+ if (stream.isDirty()) {
+ return true;
+ }
+ }
+ for (ConnectionSettings conn : connections.values()) {
+ if (conn.isDirty()) {
+ return true;
+ }
+ }
+ for (ConnectionSettings conn : networkConnectionSubIds.values()) {
+ if (conn.isDirty()) {
+ return true;
+ }
+ }
+ if (mRingMode.isDirty()) {
+ return true;
+ }
+ if (mAirplaneMode.isDirty()) {
+ return true;
+ }
+ if (mBrightness.isDirty()) {
+ return true;
+ }
+ return false;
+ }
+
+ /** @hide */
+ public void getXmlString(StringBuilder builder, Context context) {
+ builder.append("<profile ");
+ if (mNameResId > 0) {
+ builder.append("nameres=\"");
+ builder.append(context.getResources().getResourceEntryName(mNameResId));
+ } else {
+ builder.append("name=\"");
+ builder.append(TextUtils.htmlEncode(getName()));
+ }
+ builder.append("\" uuid=\"");
+ builder.append(TextUtils.htmlEncode(getUuid().toString()));
+ builder.append("\">\n");
+
+ builder.append("<uuids>");
+ for (UUID u : mSecondaryUuids) {
+ builder.append("<uuid>");
+ builder.append(TextUtils.htmlEncode(u.toString()));
+ builder.append("</uuid>");
+ }
+ builder.append("</uuids>\n");
+
+ builder.append("<profiletype>");
+ builder.append(getProfileType() == Type.TOGGLE ? "toggle" : "conditional");
+ builder.append("</profiletype>\n");
+
+ builder.append("<statusbar>");
+ builder.append(getStatusBarIndicator() ? "yes" : "no");
+ builder.append("</statusbar>\n");
+
+ if (mScreenLockMode != null) {
+ builder.append("<screen-lock-mode>");
+ mScreenLockMode.writeXmlString(builder, context);
+ builder.append("</screen-lock-mode>\n");
+ }
+
+ builder.append("<expanded-desktop-mode>");
+ builder.append(mExpandedDesktopMode);
+ builder.append("</expanded-desktop-mode>\n");
+
+ builder.append("<doze-mode>");
+ builder.append(mDozeMode);
+ builder.append("</doze-mode>\n");
+
+ builder.append("<notification-light-mode>");
+ builder.append(mNotificationLightMode);
+ builder.append("</notification-light-mode>\n");
+
+ mAirplaneMode.getXmlString(builder, context);
+
+ mBrightness.getXmlString(builder, context);
+
+ mRingMode.getXmlString(builder, context);
+
+ for (ProfileGroup pGroup : profileGroups.values()) {
+ pGroup.getXmlString(builder, context);
+ }
+ for (StreamSettings sd : streams.values()) {
+ sd.getXmlString(builder, context);
+ }
+ for (ConnectionSettings cs : connections.values()) {
+ cs.getXmlString(builder, context);
+ }
+ for (ConnectionSettings cs : networkConnectionSubIds.values()) {
+ cs.getXmlString(builder, context);
+ }
+ if (!mTriggers.isEmpty()) {
+ builder.append("<triggers>\n");
+ for (ProfileTrigger trigger : mTriggers.values()) {
+ trigger.getXmlString(builder, context);
+ }
+ builder.append("</triggers>\n");
+ }
+
+ builder.append("</profile>\n");
+ mDirty = false;
+ }
+
+ private static List<UUID> readSecondaryUuidsFromXml(XmlPullParser xpp, Context context)
+ throws XmlPullParserException,
+ IOException {
+ ArrayList<UUID> uuids = new ArrayList<UUID>();
+ int event = xpp.next();
+ while (event != XmlPullParser.END_TAG || !xpp.getName().equals("uuids")) {
+ if (event == XmlPullParser.START_TAG) {
+ String name = xpp.getName();
+ if (name.equals("uuid")) {
+ try {
+ uuids.add(UUID.fromString(xpp.nextText()));
+ } catch (NullPointerException e) {
+ Log.w(TAG, "Null Pointer - invalid UUID");
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "UUID not recognized");
+ }
+ }
+ }
+ event = xpp.next();
+ }
+ return uuids;
+ }
+
+ private static void readTriggersFromXml(XmlPullParser xpp, Context context, Profile profile)
+ throws XmlPullParserException, IOException {
+ int event = xpp.next();
+ while (event != XmlPullParser.END_TAG || !xpp.getName().equals("triggers")) {
+ if (event == XmlPullParser.START_TAG) {
+ ProfileTrigger trigger = ProfileTrigger.fromXml(xpp, context);
+ if (trigger != null) {
+ profile.mTriggers.put(trigger.mId, trigger);
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ throw new IOException("Premature end of file while parsing triggers");
+ }
+ event = xpp.next();
+ }
+ }
+
+ /** @hide */
+ public void validateRingtones(Context context) {
+ for (ProfileGroup pg : profileGroups.values()) {
+ pg.validateOverrideUris(context);
+ }
+ }
+
+ /** @hide */
+ public static Profile fromXml(XmlPullParser xpp, Context context)
+ throws XmlPullParserException, IOException {
+ String value = xpp.getAttributeValue(null, "nameres");
+ int profileNameResId = -1;
+ String profileName = null;
+
+ if (value != null) {
+ profileNameResId = context.getResources().getIdentifier(value, "string",
+ "cyanogenmod.platform");
+ if (profileNameResId > 0) {
+ profileName = context.getResources().getString(profileNameResId);
+ }
+ }
+
+ if (profileName == null) {
+ profileName = xpp.getAttributeValue(null, "name");
+ }
+
+ UUID profileUuid = UUID.randomUUID();
+ try {
+ profileUuid = UUID.fromString(xpp.getAttributeValue(null, "uuid"));
+ } catch (NullPointerException e) {
+ Log.w(TAG,
+ "Null Pointer - UUID not found for "
+ + profileName
+ + ". New UUID generated: "
+ + profileUuid.toString()
+ );
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG,
+ "UUID not recognized for "
+ + profileName
+ + ". New UUID generated: "
+ + profileUuid.toString()
+ );
+ }
+
+ Profile profile = new Profile(profileName, profileNameResId, profileUuid);
+ int event = xpp.next();
+ while (event != XmlPullParser.END_TAG) {
+ if (event == XmlPullParser.START_TAG) {
+ String name = xpp.getName();
+ if (name.equals("uuids")) {
+ profile.setSecondaryUuids(readSecondaryUuidsFromXml(xpp, context));
+ }
+ if (name.equals("statusbar")) {
+ profile.setStatusBarIndicator(xpp.nextText().equals("yes"));
+ }
+ if (name.equals("profiletype")) {
+ profile.setProfileType(xpp.nextText().equals("toggle")
+ ? Type.TOGGLE : Type.CONDITIONAL);
+ }
+ if (name.equals("ringModeDescriptor")) {
+ RingModeSettings smd = RingModeSettings.fromXml(xpp, context);
+ profile.setRingMode(smd);
+ }
+ if (name.equals("airplaneModeDescriptor")) {
+ AirplaneModeSettings amd = AirplaneModeSettings.fromXml(xpp, context);
+ profile.setAirplaneMode(amd);
+ }
+ if (name.equals("brightnessDescriptor")) {
+ BrightnessSettings bd = BrightnessSettings.fromXml(xpp, context);
+ profile.setBrightness(bd);
+ }
+ if (name.equals("screen-lock-mode")) {
+ LockSettings lockMode = new LockSettings(Integer.valueOf(xpp.nextText()));
+ profile.setScreenLockMode(lockMode);
+ }
+ if (name.equals("expanded-desktop-mode")) {
+ profile.setExpandedDesktopMode(Integer.valueOf(xpp.nextText()));
+ }
+ if (name.equals("doze-mode")) {
+ profile.setDozeMode(Integer.valueOf(xpp.nextText()));
+ }
+ if (name.equals("notification-light-mode")) {
+ profile.setNotificationLightMode(Integer.valueOf(xpp.nextText()));
+ }
+ if (name.equals("profileGroup")) {
+ ProfileGroup pg = ProfileGroup.fromXml(xpp, context);
+ profile.addProfileGroup(pg);
+ }
+ if (name.equals("streamDescriptor")) {
+ StreamSettings sd = StreamSettings.fromXml(xpp, context);
+ profile.setStreamSettings(sd);
+ }
+ if (name.equals("connectionDescriptor")) {
+ ConnectionSettings cs = ConnectionSettings.fromXml(xpp, context);
+ if (Build.CM_VERSION.SDK_INT >= Build.CM_VERSION_CODES.ELDERBERRY
+ && cs.getConnectionId() == ConnectionSettings.PROFILE_CONNECTION_2G3G4G) {
+ profile.networkConnectionSubIds.put(cs.getSubId(), cs);
+ } else {
+ profile.connections.put(cs.getConnectionId(), cs);
+ }
+ }
+ if (name.equals("triggers")) {
+ readTriggersFromXml(xpp, context, profile);
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ throw new IOException("Premature end of file while parsing profle:" + profileName);
+ }
+ event = xpp.next();
+ }
+
+ /* we just loaded from XML, so nothing needs saving */
+ profile.mDirty = false;
+
+ return profile;
+ }
+
+ /** @hide */
+ public void doSelect(Context context, IKeyguardService keyguardService) {
+ // Set stream volumes
+ AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ for (StreamSettings sd : streams.values()) {
+ if (sd.isOverride()) {
+ am.setStreamVolume(sd.getStreamId(), sd.getValue(), 0);
+ }
+ }
+ // Set connections
+ for (ConnectionSettings cs : connections.values()) {
+ if (cs.isOverride()) {
+ cs.processOverride(context);
+ }
+ }
+ for (ConnectionSettings cs : networkConnectionSubIds.values()) {
+ if (cs.isOverride()) {
+ cs.processOverride(context);
+ }
+ }
+
+ // Set ring mode
+ mRingMode.processOverride(context);
+ // Set airplane mode
+ mAirplaneMode.processOverride(context);
+
+ // Set brightness
+ mBrightness.processOverride(context);
+
+ if (keyguardService != null) {
+ // Set lock screen mode
+ mScreenLockMode.processOverride(context, keyguardService);
+ } else {
+ Log.e(TAG, "cannot process screen lock override without a keyguard service.");
+ }
+
+ // Set expanded desktop
+ // if (mExpandedDesktopMode != ExpandedDesktopMode.DEFAULT) {
+ // Settings.System.putIntForUser(context.getContentResolver(),
+ // Settings.System.EXPANDED_DESKTOP_STATE,
+ // mExpandedDesktopMode == ExpandedDesktopMode.ENABLE ? 1 : 0,
+ // UserHandle.USER_CURRENT);
+ // }
+
+ // Set doze mode
+ if (mDozeMode != DozeMode.DEFAULT) {
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.DOZE_ENABLED,
+ mDozeMode == DozeMode.ENABLE ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ // Set notification light mode
+ if (mNotificationLightMode != NotificationLightMode.DEFAULT) {
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.NOTIFICATION_LIGHT_PULSE,
+ mNotificationLightMode == NotificationLightMode.ENABLE ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+ }
+
+ /**
+ * Get the settings for a stream id in the {@link Profile}
+ * @return {@link StreamSettings}
+ */
+ public StreamSettings getSettingsForStream(int streamId){
+ return streams.get(streamId);
+ }
+
+ /**
+ * Set the {@link StreamSettings} for the {@link Profile}
+ * @param descriptor
+ */
+ public void setStreamSettings(StreamSettings descriptor){
+ streams.put(descriptor.getStreamId(), descriptor);
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link StreamSettings} for the {@link Profile}
+ * @return {@link Collection<StreamSettings>}
+ */
+ public Collection<StreamSettings> getStreamSettings(){
+ return streams.values();
+ }
+
+ /**
+ * Get the settings for a connection id in the {@link Profile}
+ * @return {@link ConnectionSettings}
+ */
+ public ConnectionSettings getSettingsForConnection(int connectionId){
+ if (connectionId == ConnectionSettings.PROFILE_CONNECTION_2G3G4G) {
+ if (networkConnectionSubIds.size() > 1) {
+ throw new UnsupportedOperationException("Use getConnectionSettingsWithSubId for MSIM devices!");
+ } else {
+ return networkConnectionSubIds.values().iterator().next();
+ }
+ }
+ return connections.get(connectionId);
+ }
+
+ /**
+ * Get the settings for a {@link ConnectionSettings#PROFILE_CONNECTION_2G3G4G} by sub id.
+ *
+ * @param subId the sub id to lookup. Can be {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID}
+ * @return {@link ConnectionSettings}
+ */
+ public ConnectionSettings getConnectionSettingWithSubId(int subId) {
+ return networkConnectionSubIds.get(subId);
+ }
+
+ /**
+ * Set the {@link ConnectionSettings} for the {@link Profile}
+ * @param descriptor
+ */
+ public void setConnectionSettings(ConnectionSettings descriptor) {
+ if (descriptor.getConnectionId() == ConnectionSettings.PROFILE_CONNECTION_2G3G4G) {
+ networkConnectionSubIds.put(descriptor.getSubId(), descriptor);
+ } else {
+ connections.put(descriptor.getConnectionId(), descriptor);
+ }
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link ConnectionSettings} for the {@link Profile}
+ * @return {@link Collection<ConnectionSettings>}
+ */
+ public Collection<ConnectionSettings> getConnectionSettings(){
+ List<ConnectionSettings> combinedList = new ArrayList<>();
+ combinedList.addAll(connections.values());
+ combinedList.addAll(networkConnectionSubIds.values());
+ return combinedList;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/ProfileGroup.java b/sdk/src/java/cyanogenmod/app/ProfileGroup.java
new file mode 100644
index 0000000..56ec507
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ProfileGroup.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2015 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 cyanogenmod.app;
+
+import android.app.Notification;
+import android.app.NotificationGroup;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelUuid;
+import android.text.TextUtils;
+import android.util.Log;
+
+import cyanogenmod.os.Build;
+
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * @hide
+ * TODO: This isn't ready for public use
+ */
+public final class ProfileGroup implements Parcelable {
+ private static final String TAG = "ProfileGroup";
+
+ private String mName;
+ private int mNameResId;
+
+ private UUID mUuid;
+
+ private Uri mSoundOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+ private Uri mRingerOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+
+ private Mode mSoundMode = Mode.DEFAULT;
+ private Mode mRingerMode = Mode.DEFAULT;
+ private Mode mVibrateMode = Mode.DEFAULT;
+ private Mode mLightsMode = Mode.DEFAULT;
+
+ private boolean mDefaultGroup = false;
+ private boolean mDirty;
+
+ /** @hide */
+ public static final Parcelable.Creator<ProfileGroup> CREATOR
+ = new Parcelable.Creator<ProfileGroup>() {
+ public ProfileGroup createFromParcel(Parcel in) {
+ return new ProfileGroup(in);
+ }
+
+ @Override
+ public ProfileGroup[] newArray(int size) {
+ return new ProfileGroup[size];
+ }
+ };
+
+ /** @hide */
+ public ProfileGroup(UUID uuid, boolean defaultGroup) {
+ this(null, uuid, defaultGroup);
+ }
+
+ private ProfileGroup(String name, UUID uuid, boolean defaultGroup) {
+ mName = name;
+ mUuid = (uuid != null) ? uuid : UUID.randomUUID();
+ mDefaultGroup = defaultGroup;
+ mDirty = uuid == null;
+ }
+
+ /** @hide */
+ private ProfileGroup(Parcel in) {
+ readFromParcel(in);
+ }
+
+ /** @hide */
+ public boolean matches(NotificationGroup group, boolean defaultGroup) {
+ if (mUuid.equals(group.getUuid())) {
+ return true;
+ }
+
+ /* fallback matches for backwards compatibility */
+ boolean matches = false;
+
+ /* fallback attempt 1: match name */
+ if (mName != null && mName.equals(group.getName())) {
+ matches = true;
+ /* fallback attempt 2: match for the 'defaultGroup' flag to match the wildcard group */
+ } else if (mDefaultGroup && defaultGroup) {
+ matches = true;
+ }
+
+ if (!matches) {
+ return false;
+ }
+
+ mName = null;
+ mUuid = group.getUuid();
+ mDirty = true;
+
+ return true;
+ }
+
+ public UUID getUuid() {
+ return mUuid;
+ }
+
+ public boolean isDefaultGroup() {
+ return mDefaultGroup;
+ }
+
+ /** @hide */
+ public boolean isDirty() {
+ return mDirty;
+ }
+
+ /** @hide */
+ public void setSoundOverride(Uri sound) {
+ mSoundOverride = sound;
+ mDirty = true;
+ }
+
+ public Uri getSoundOverride() {
+ return mSoundOverride;
+ }
+
+ /** @hide */
+ public void setRingerOverride(Uri ringer) {
+ mRingerOverride = ringer;
+ mDirty = true;
+ }
+
+ public Uri getRingerOverride() {
+ return mRingerOverride;
+ }
+
+ /** @hide */
+ public void setSoundMode(Mode soundMode) {
+ mSoundMode = soundMode;
+ mDirty = true;
+ }
+
+ public Mode getSoundMode() {
+ return mSoundMode;
+ }
+
+ /** @hide */
+ public void setRingerMode(Mode ringerMode) {
+ mRingerMode = ringerMode;
+ mDirty = true;
+ }
+
+ public Mode getRingerMode() {
+ return mRingerMode;
+ }
+
+ /** @hide */
+ public void setVibrateMode(Mode vibrateMode) {
+ mVibrateMode = vibrateMode;
+ mDirty = true;
+ }
+
+ public Mode getVibrateMode() {
+ return mVibrateMode;
+ }
+
+ /** @hide */
+ public void setLightsMode(Mode lightsMode) {
+ mLightsMode = lightsMode;
+ mDirty = true;
+ }
+
+ public Mode getLightsMode() {
+ return mLightsMode;
+ }
+
+ // TODO : add support for LEDs / screen etc.
+
+ /** @hide */
+ public void applyOverridesToNotification(Notification notification) {
+ switch (mSoundMode) {
+ case OVERRIDE:
+ notification.sound = mSoundOverride;
+ break;
+ case SUPPRESS:
+ notification.defaults &= ~Notification.DEFAULT_SOUND;
+ notification.sound = null;
+ break;
+ case DEFAULT:
+ break;
+ }
+ switch (mVibrateMode) {
+ case OVERRIDE:
+ notification.defaults |= Notification.DEFAULT_VIBRATE;
+ break;
+ case SUPPRESS:
+ notification.defaults &= ~Notification.DEFAULT_VIBRATE;
+ notification.vibrate = null;
+ break;
+ case DEFAULT:
+ break;
+ }
+ switch (mLightsMode) {
+ case OVERRIDE:
+ notification.defaults |= Notification.DEFAULT_LIGHTS;
+ break;
+ case SUPPRESS:
+ notification.defaults &= ~Notification.DEFAULT_LIGHTS;
+ notification.flags &= ~Notification.FLAG_SHOW_LIGHTS;
+ break;
+ case DEFAULT:
+ break;
+ }
+ }
+
+ private boolean validateOverrideUri(Context context, Uri uri) {
+ if (RingtoneManager.isDefault(uri)) {
+ return true;
+ }
+ Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
+ boolean valid = false;
+
+ if (cursor != null) {
+ valid = cursor.moveToFirst();
+ cursor.close();
+ }
+ return valid;
+ }
+
+ void validateOverrideUris(Context context) {
+ if (!validateOverrideUri(context, mSoundOverride)) {
+ mSoundOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+ mSoundMode = Mode.DEFAULT;
+ mDirty = true;
+ }
+ if (!validateOverrideUri(context, mRingerOverride)) {
+ mRingerOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+ mRingerMode = Mode.DEFAULT;
+ mDirty = true;
+ }
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // === BOYSENBERRY ===
+ dest.writeString(mName);
+ new ParcelUuid(mUuid).writeToParcel(dest, 0);
+ dest.writeInt(mDefaultGroup ? 1 : 0);
+ dest.writeInt(mDirty ? 1 : 0);
+ dest.writeParcelable(mSoundOverride, flags);
+ dest.writeParcelable(mRingerOverride, flags);
+ dest.writeString(mSoundMode.name());
+ dest.writeString(mRingerMode.name());
+ dest.writeString(mVibrateMode.name());
+ dest.writeString(mLightsMode.name());
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /** @hide */
+ public void readFromParcel(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ mName = in.readString();
+ mUuid = ParcelUuid.CREATOR.createFromParcel(in).getUuid();
+ mDefaultGroup = in.readInt() != 0;
+ mDirty = in.readInt() != 0;
+ mSoundOverride = in.readParcelable(null);
+ mRingerOverride = in.readParcelable(null);
+
+ mSoundMode = Mode.valueOf(Mode.class, in.readString());
+ mRingerMode = Mode.valueOf(Mode.class, in.readString());
+ mVibrateMode = Mode.valueOf(Mode.class, in.readString());
+ mLightsMode = Mode.valueOf(Mode.class, in.readString());
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ public enum Mode {
+ SUPPRESS, DEFAULT, OVERRIDE;
+ }
+
+ /** @hide */
+ public void getXmlString(StringBuilder builder, Context context) {
+ builder.append("<profileGroup uuid=\"");
+ builder.append(TextUtils.htmlEncode(mUuid.toString()));
+ if (mName != null) {
+ builder.append("\" name=\"");
+ builder.append(mName);
+ }
+ builder.append("\" default=\"");
+ builder.append(isDefaultGroup());
+ builder.append("\">\n<sound>");
+ builder.append(TextUtils.htmlEncode(mSoundOverride.toString()));
+ builder.append("</sound>\n<ringer>");
+ builder.append(TextUtils.htmlEncode(mRingerOverride.toString()));
+ builder.append("</ringer>\n<soundMode>");
+ builder.append(mSoundMode);
+ builder.append("</soundMode>\n<ringerMode>");
+ builder.append(mRingerMode);
+ builder.append("</ringerMode>\n<vibrateMode>");
+ builder.append(mVibrateMode);
+ builder.append("</vibrateMode>\n<lightsMode>");
+ builder.append(mLightsMode);
+ builder.append("</lightsMode>\n</profileGroup>\n");
+ mDirty = false;
+ }
+
+ /** @hide */
+ public static ProfileGroup fromXml(XmlPullParser xpp, Context context)
+ throws XmlPullParserException, IOException {
+ String name = xpp.getAttributeValue(null, "name");
+ UUID uuid = null;
+ String value = xpp.getAttributeValue(null, "uuid");
+
+ if (value != null) {
+ try {
+ uuid = UUID.fromString(value);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "UUID not recognized for " + name + ", using new one.");
+ }
+ }
+
+ value = xpp.getAttributeValue(null, "default");
+ boolean defaultGroup = TextUtils.equals(value, "true");
+
+ ProfileGroup profileGroup = new ProfileGroup(name, uuid, defaultGroup);
+ int event = xpp.next();
+ while (event != XmlPullParser.END_TAG || !xpp.getName().equals("profileGroup")) {
+ if (event == XmlPullParser.START_TAG) {
+ name = xpp.getName();
+ if (name.equals("sound")) {
+ profileGroup.setSoundOverride(Uri.parse(xpp.nextText()));
+ } else if (name.equals("ringer")) {
+ profileGroup.setRingerOverride(Uri.parse(xpp.nextText()));
+ } else if (name.equals("soundMode")) {
+ profileGroup.setSoundMode(Mode.valueOf(xpp.nextText()));
+ } else if (name.equals("ringerMode")) {
+ profileGroup.setRingerMode(Mode.valueOf(xpp.nextText()));
+ } else if (name.equals("vibrateMode")) {
+ profileGroup.setVibrateMode(Mode.valueOf(xpp.nextText()));
+ } else if (name.equals("lightsMode")) {
+ profileGroup.setLightsMode(Mode.valueOf(xpp.nextText()));
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ throw new IOException("Premature end of file while parsing profleGroup:" + name);
+ }
+ event = xpp.next();
+ }
+
+ /* we just loaded from XML, no need to save */
+ profileGroup.mDirty = false;
+
+ return profileGroup;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/ProfileManager.java b/sdk/src/java/cyanogenmod/app/ProfileManager.java
new file mode 100644
index 0000000..c2470cb
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ProfileManager.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 2015 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 cyanogenmod.app;
+
+import java.util.UUID;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.NotificationGroup;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import cyanogenmod.app.IProfileManager;
+
+import com.android.internal.R;
+import cyanogenmod.providers.CMSettings;
+
+
+/**
+ * <p>
+ * The ProfileManager allows you to create {@link Profile}s and {@link ProfileGroup}s to create
+ * specific behavior states depending on triggers from hardware devices changing states, such as:
+ *
+ * <pre class="prettyprint">
+ * WiFi being enabled
+ * WiFi connecting to a certain AP
+ * Bluetooth connecting to a certain device
+ * Bluetooth disconnecting to a certain device
+ * NFC tag being scanned
+ * </pre>
+ *
+ * <p>
+ * Depending on these triggers, you can override connection settings, lockscreen modes, media
+ * stream volumes and various other settings.
+ *
+ * <p>
+ * To get the instance of this class, utilize ProfileManager#getInstance(Context context)
+ *
+ * <p>
+ * This manager requires the MODIFY_PROFILES permission.
+ *
+ * @see cyanogenmod.app.Profile
+ * @see cyanogenmod.app.ProfileGroup
+ */
+public class ProfileManager {
+
+ private static IProfileManager sService;
+
+ private Context mContext;
+
+ private static final String TAG = "ProfileManager";
+
+ /**
+ * <p>Broadcast Action: A new profile has been selected. This can be triggered by the user
+ * or by calls to the ProfileManagerService / Profile.</p>
+ */
+ public static final String INTENT_ACTION_PROFILE_SELECTED =
+ "cyanogenmod.platform.intent.action.PROFILE_SELECTED";
+
+ /**
+ * <p>Broadcast Action: Current profile has been updated. This is triggered every time the
+ * currently active profile is updated, instead of selected.</p>
+ * <p> For instance, this includes profile updates caused by a locale change, which doesn't
+ * trigger a profile selection, but causes its name to change.</p>
+ */
+ public static final String INTENT_ACTION_PROFILE_UPDATED =
+ "cyanogenmod.platform.intent.action.PROFILE_UPDATED";
+
+ /**
+ * Extra for {@link #INTENT_ACTION_PROFILE_SELECTED} and {@link #INTENT_ACTION_PROFILE_UPDATED}:
+ * The name of the newly activated or updated profile
+ */
+ public static final String EXTRA_PROFILE_NAME = "name";
+
+ /**
+ * Extra for {@link #INTENT_ACTION_PROFILE_SELECTED} and {@link #INTENT_ACTION_PROFILE_UPDATED}:
+ * The string representation of the UUID of the newly activated or updated profile
+ */
+ public static final String EXTRA_PROFILE_UUID = "uuid";
+
+ /**
+ * Extra for {@link #INTENT_ACTION_PROFILE_SELECTED}:
+ * The name of the previously active profile
+ */
+ public static final String EXTRA_LAST_PROFILE_NAME = "lastName";
+
+ /**
+ * Extra for {@link #INTENT_ACTION_PROFILE_SELECTED}:
+ * The string representation of the UUID of the previously active profile
+ */
+ public static final String EXTRA_LAST_PROFILE_UUID = "lastUuid";
+
+ /**
+ * Activity Action: Shows a profile picker.
+ * <p>
+ * Input: {@link #EXTRA_PROFILE_EXISTING_UUID}, {@link #EXTRA_PROFILE_SHOW_NONE},
+ * {@link #EXTRA_PROFILE_TITLE}.
+ * <p>
+ * Output: {@link #EXTRA_PROFILE_PICKED_UUID}.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PROFILE_PICKER =
+ "cyanogenmod.platform.intent.action.PROFILE_PICKER";
+
+ /**
+ * Constant for NO_PROFILE
+ */
+ public static final UUID NO_PROFILE =
+ UUID.fromString("00000000-0000-0000-0000-000000000000");
+
+ /**
+ * Given to the profile picker as a boolean. Whether to show an item for
+ * deselect the profile. If the "None" item is picked,
+ * {@link #EXTRA_PROFILE_PICKED_UUID} will be {@link #NO_PROFILE}.
+ *
+ * @see #ACTION_PROFILE_PICKER
+ */
+ public static final String EXTRA_PROFILE_SHOW_NONE =
+ "cyanogenmod.platform.intent.extra.profile.SHOW_NONE";
+
+ /**
+ * Given to the profile picker as a {@link UUID} string representation. The {@link UUID}
+ * representation of the current profile, which will be used to show a checkmark next to
+ * the item for this {@link UUID}. If the item is {@link #NO_PROFILE} then "None" item
+ * is selected if {@link #EXTRA_PROFILE_SHOW_NONE} is enabled. Otherwise, the current
+ * profile is selected.
+ *
+ * @see #ACTION_PROFILE_PICKER
+ */
+ public static final String EXTRA_PROFILE_EXISTING_UUID =
+ "cyanogenmod.platform.extra.profile.EXISTING_UUID";
+
+ /**
+ * Given to the profile picker as a {@link CharSequence}. The title to
+ * show for the profile picker. This has a default value that is suitable
+ * in most cases.
+ *
+ * @see #ACTION_PROFILE_PICKER
+ */
+ public static final String EXTRA_PROFILE_TITLE =
+ "cyanogenmod.platform.intent.extra.profile.TITLE";
+
+ /**
+ * Returned from the profile picker as a {@link UUID} string representation.
+ * <p>
+ * It will be one of:
+ * <li> the picked profile,
+ * <li> null if the "None" item was picked.
+ *
+ * @see #ACTION_PROFILE_PICKER
+ */
+ public static final String EXTRA_PROFILE_PICKED_UUID =
+ "cyanogenmod.platform.intent.extra.profile.PICKED_UUID";
+
+ /**
+ * Broadcast intent action indicating that Profiles has been enabled or disabled.
+ * One extra provides this state as an int.
+ *
+ * @see #EXTRA_PROFILES_STATE
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String PROFILES_STATE_CHANGED_ACTION =
+ "cyanogenmod.platform.app.profiles.PROFILES_STATE_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates whether Profiles are enabled or
+ * disabled. Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}.
+ *
+ * @see #PROFILES_STATE_DISABLED
+ * @see #PROFILES_STATE_ENABLED
+ */
+ public static final String EXTRA_PROFILES_STATE = "profile_state";
+
+ /**
+ * Set the resource id theme to use for the dialog picker activity.<br/>
+ * The default theme is <code>com.android.internal.R.Theme_Holo_Dialog_Alert</code>.
+ *
+ * @see #ACTION_PROFILE_PICKER
+ */
+ public static final String EXTRA_PROFILE_DIALOG_THEME =
+ "cyanogenmod.platform.intent.extra.profile.DIALOG_THEME";
+
+ /**
+ * Profiles are disabled.
+ *
+ * @see #PROFILES_STATE_CHANGED_ACTION
+ */
+ public static final int PROFILES_STATE_DISABLED = 0;
+
+ /**
+ * Profiles are enabled.
+ *
+ * @see #PROFILES_STATE_CHANGED_ACTION
+ */
+ public static final int PROFILES_STATE_ENABLED = 1;
+
+ private static ProfileManager sProfileManagerInstance;
+ private ProfileManager(Context context) {
+ Context appContext = context.getApplicationContext();
+ if (appContext != null) {
+ mContext = appContext;
+ } else {
+ mContext = context;
+ }
+ sService = getService();
+
+ if (context.getPackageManager().hasSystemFeature(
+ cyanogenmod.app.CMContextConstants.Features.PROFILES) && sService == null) {
+ throw new RuntimeException("Unable to get ProfileManagerService. The service either" +
+ " crashed, was not started, or the interface has been called to early in" +
+ " SystemServer init");
+ }
+ }
+
+ /**
+ * Get or create an instance of the {@link cyanogenmod.app.ProfileManager}
+ * @param context
+ * @return {@link ProfileManager}
+ */
+ public static ProfileManager getInstance(Context context) {
+ if (sProfileManagerInstance == null) {
+ sProfileManagerInstance = new ProfileManager(context);
+ }
+ return sProfileManagerInstance;
+ }
+
+ /** @hide */
+ static public IProfileManager getService() {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_PROFILE_SERVICE);
+ sService = IProfileManager.Stub.asInterface(b);
+ return sService;
+ }
+
+ @Deprecated
+ public void setActiveProfile(String profileName) {
+ try {
+ getService().setActiveProfileByName(profileName);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Set the active {@link Profile} by {@link UUID}
+ * @param profileUuid the {@link UUID} associated with the profile
+ */
+ public void setActiveProfile(UUID profileUuid) {
+ try {
+ getService().setActiveProfile(new ParcelUuid(profileUuid));
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Get the active {@link Profile}
+ * @return active {@link Profile}
+ */
+ public Profile getActiveProfile() {
+ try {
+ return getService().getActiveProfile();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Add a {@link Profile} that can be selected by the user
+ * @param profile a {@link Profile} object
+ */
+ public void addProfile(Profile profile) {
+ try {
+ getService().addProfile(profile);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Remove a {@link Profile} from user selection
+ * @param profile a {@link Profile} object
+ */
+ public void removeProfile(Profile profile) {
+ try {
+ getService().removeProfile(profile);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Update a {@link Profile} object
+ * @param profile a {@link Profile} object
+ */
+ public void updateProfile(Profile profile) {
+ try {
+ getService().updateProfile(profile);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Get the {@link Profile} object by its literal name
+ * @param profileName name associated with the profile
+ * @return profile a {@link Profile} object
+ */
+ @Deprecated
+ public Profile getProfile(String profileName) {
+ try {
+ return getService().getProfileByName(profileName);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Get a {@link Profile} via {@link UUID}
+ * @param profileUuid {@link UUID} associated with the profile
+ * @return {@link Profile}
+ */
+ public Profile getProfile(UUID profileUuid) {
+ try {
+ return getService().getProfile(new ParcelUuid(profileUuid));
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Get the profile names currently available to the user
+ * @return {@link String[]} of profile names
+ */
+ public String[] getProfileNames() {
+ try {
+ Profile[] profiles = getService().getProfiles();
+ String[] names = new String[profiles.length];
+ for (int i = 0; i < profiles.length; i++) {
+ names[i] = profiles[i].getName();
+ }
+ return names;
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Get the {@link Profile}s currently available to the user
+ * @return {@link Profile[]}
+ */
+ public Profile[] getProfiles() {
+ try {
+ return getService().getProfiles();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Check if a {@link Profile} exists via its literal name
+ * @param profileName a profile name
+ * @return whether or not the profile exists
+ */
+ public boolean profileExists(String profileName) {
+ try {
+ return getService().profileExistsByName(profileName);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ // To be on the safe side, we'll return "true", to prevent duplicate profiles
+ // from being created.
+ return true;
+ }
+ }
+
+ /**
+ * Check if a {@link Profile} exists via its {@link UUID}
+ * @param profileUuid the profiles {@link UUID}
+ * @return whether or not the profile exists
+ */
+ public boolean profileExists(UUID profileUuid) {
+ try {
+ return getService().profileExists(new ParcelUuid(profileUuid));
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ // To be on the safe side, we'll return "true", to prevent duplicate profiles
+ // from being created.
+ return true;
+ }
+ }
+
+ /**
+ * Check if a NotificationGroup exists
+ * @param notificationGroupName the name of the notification group
+ * @return whether or not the notification group exists
+ * @hide
+ */
+ public boolean notificationGroupExists(String notificationGroupName) {
+ try {
+ return getService().notificationGroupExistsByName(notificationGroupName);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ // To be on the safe side, we'll return "true", to prevent duplicate notification
+ // groups from being created.
+ return true;
+ }
+ }
+
+ /**
+ * Get the currently available NotificationGroups
+ * @return NotificationGroup
+ * @hide
+ */
+ public NotificationGroup[] getNotificationGroups() {
+ try {
+ return getService().getNotificationGroups();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Add a NotificationGroup to the available list
+ * @param group NotificationGroup
+ * @hide
+ */
+ public void addNotificationGroup(NotificationGroup group) {
+ try {
+ getService().addNotificationGroup(group);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Remove a NotificationGroup from the available list
+ * @param group NotificationGroup
+ * @hide
+ */
+ public void removeNotificationGroup(NotificationGroup group) {
+ try {
+ getService().removeNotificationGroup(group);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Update a NotificationGroup from the available list
+ * @param group NotificationGroup
+ * @hide
+ */
+ public void updateNotificationGroup(NotificationGroup group) {
+ try {
+ getService().updateNotificationGroup(group);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Get a NotificationGroup for a specific package
+ * @param pkg name of the package
+ * @hide
+ */
+ public NotificationGroup getNotificationGroupForPackage(String pkg) {
+ try {
+ return getService().getNotificationGroupForPackage(pkg);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Get a NotificationGroup from the available list via {@link UUID}
+ * @param uuid {@link UUID} of the notification group
+ * @hide
+ */
+ public NotificationGroup getNotificationGroup(UUID uuid) {
+ try {
+ return getService().getNotificationGroup(new ParcelUuid(uuid));
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Get an active {@link ProfileGroup} via its package name
+ * @param packageName the package name associated to the profile group
+ * @return {@link ProfileGroup}
+ * @hide
+ */
+ public ProfileGroup getActiveProfileGroup(String packageName) {
+ NotificationGroup notificationGroup = getNotificationGroupForPackage(packageName);
+ if (notificationGroup == null) {
+ ProfileGroup defaultGroup = getActiveProfile().getDefaultGroup();
+ return defaultGroup;
+ }
+ return getActiveProfile().getProfileGroup(notificationGroup.getUuid());
+ }
+
+ /**
+ * Reset all profiles, groups, and notification groups to default state
+ */
+ public void resetAll() {
+ try {
+ getService().resetAll();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ } catch (SecurityException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Check if profiles are currently activated in the system
+ * @return whether profiles are enabled
+ */
+ public boolean isProfilesEnabled() {
+ try {
+ return getService().isEnabled();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return false;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.aidl b/sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.aidl
new file mode 100644
index 0000000..96cfb6a
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app;
+
+parcelable StatusBarPanelCustomTile;
+
diff --git a/sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.java b/sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.java
new file mode 100644
index 0000000..7710e5a
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2015 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 cyanogenmod.app;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import cyanogenmod.os.Build;
+
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+/**
+ * Class encapsulating a Custom Tile. Sent by the StatusBarManagerService to clients including
+ * the status bar panel and any {@link cyanogenmod.app.CustomTileListenerService} clients.
+ */
+public class StatusBarPanelCustomTile implements Parcelable {
+
+ private final String pkg;
+ private final int id;
+ private final String tag;
+ private final String key;
+
+ private final int uid;
+ private final String resPkg;
+ private final String opPkg;
+ private final int initialPid;
+ private final CustomTile customTile;
+ private final UserHandle user;
+ private final long postTime;
+
+ public StatusBarPanelCustomTile(String pkg, String resPkg, String opPkg, int id, String tag,
+ int uid, int initialPid, CustomTile customTile, UserHandle user) {
+ this(pkg, resPkg, opPkg, id, tag, uid, initialPid, customTile, user,
+ System.currentTimeMillis());
+ }
+
+ public StatusBarPanelCustomTile(String pkg, String resPkg, String opPkg, int id, String tag,
+ int uid, int initialPid, CustomTile customTile, UserHandle user,
+ long postTime) {
+ if (pkg == null) throw new NullPointerException();
+ if (customTile == null) throw new NullPointerException();
+
+ this.pkg = pkg;
+ this.resPkg = resPkg;
+ this.opPkg = opPkg;
+ this.id = id;
+ this.tag = tag;
+ this.uid = uid;
+ this.initialPid = initialPid;
+ this.customTile = customTile;
+ this.user = user;
+ this.postTime = postTime;
+ this.key = key();
+ }
+
+
+ public StatusBarPanelCustomTile(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // tmp variables for final
+ String tmpResPkg = null;
+ String tmpPkg = null;
+ String tmpOpPkg = null;
+ int tmpId = -1;
+ String tmpTag = null;
+ int tmpUid = -1;
+ int tmpPid = -1;
+ CustomTile tmpCustomTile = null;
+ UserHandle tmpUser = null;
+ long tmpPostTime = -1;
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+ // default
+ tmpPkg = in.readString();
+ tmpOpPkg = in.readString();
+ tmpId = in.readInt();
+ if (in.readInt() != 0) {
+ tmpTag = in.readString();
+ } else {
+ tmpTag = null;
+ }
+ tmpUid = in.readInt();
+ tmpPid = in.readInt();
+ tmpCustomTile = new CustomTile(in);
+ tmpUser = UserHandle.readFromParcel(in);
+ tmpPostTime = in.readLong();
+ }
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ tmpResPkg = in.readString();
+ }
+
+ // Assign finals
+ this.resPkg = tmpResPkg;
+ this.pkg = tmpPkg;
+ this.opPkg = tmpOpPkg;
+ this.id = tmpId;
+ this.tag = tmpTag;
+ this.uid = tmpUid;
+ this.initialPid = tmpPid;
+ this.customTile = tmpCustomTile;
+ this.user = tmpUser;
+ this.postTime = tmpPostTime;
+ this.key = key();
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ private String key() {
+ return user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid;
+ }
+
+ public static final Creator<StatusBarPanelCustomTile> CREATOR
+ = new Creator<StatusBarPanelCustomTile>()
+ {
+ public StatusBarPanelCustomTile createFromParcel(Parcel parcel)
+ {
+ return new StatusBarPanelCustomTile(parcel);
+ }
+
+ public StatusBarPanelCustomTile[] newArray(int size)
+ {
+ return new StatusBarPanelCustomTile[size];
+ }
+ };
+
+ /** The {@link cyanogenmod.app.CustomTile} supplied to
+ * {@link cyanogenmod.app.CMStatusBarManager#publishTile(int, cyanogenmod.app.CustomTile)}.
+ */
+ public CustomTile getCustomTile() {
+ return customTile;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(out);
+
+ // ==== APRICOT ===
+ out.writeString(this.pkg);
+ out.writeString(this.opPkg);
+ out.writeInt(this.id);
+ if (this.tag != null) {
+ out.writeInt(1);
+ out.writeString(this.tag);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeInt(this.uid);
+ out.writeInt(this.initialPid);
+ this.customTile.writeToParcel(out, flags);
+ user.writeToParcel(out, flags);
+ out.writeLong(this.postTime);
+
+ // ==== BOYSENBERRY =====
+ out.writeString(this.resPkg);
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public StatusBarPanelCustomTile clone() {
+ return new StatusBarPanelCustomTile(this.pkg, this.resPkg, this.opPkg,
+ this.id, this.tag, this.uid, this.initialPid,
+ this.customTile.clone(), this.user, this.postTime);
+ }
+
+ /**
+ * Returns a userHandle for the instance of the app that posted this tile.
+ */
+ public int getUserId() {
+ return this.user.getIdentifier();
+ }
+
+ /** The package of the app that posted the tile */
+ public String getPackage() {
+ return pkg;
+ }
+
+ /** The id supplied to CMStatusBarManager */
+ public int getId() {
+ return id;
+ }
+
+ /** The tag supplied to CMStatusBarManager or null if no tag was specified. */
+ public String getTag() {
+ return tag;
+ }
+
+ /**
+ * A unique instance key for this tile record.
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /** The notifying app's calling uid. @hide */
+ public int getUid() {
+ return uid;
+ }
+
+ /** The package used for load resources from. @hide */
+ public String getResPkg() {
+ return resPkg;
+ }
+
+ /** The package used for AppOps tracking. @hide */
+ public String getOpPkg() {
+ return opPkg;
+ }
+
+ /** @hide */
+ public int getInitialPid() {
+ return initialPid;
+ }
+
+ /**
+ * The {@link android.os.UserHandle} for whom this CustomTile is intended.
+ */
+ public UserHandle getUser() {
+ return user;
+ }
+
+ /** The time (in {@link System#currentTimeMillis} time) the CustomTile was published, */
+ public long getPostTime() {
+ return postTime;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/ThemeComponent.java b/sdk/src/java/cyanogenmod/app/ThemeComponent.java
new file mode 100644
index 0000000..d3f6625
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ThemeComponent.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 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 cyanogenmod.app;
+
+/**
+ * The id value here matches the framework. Unknown is given a -1 value since future
+ * framework components will always be positive.
+ * @hide
+ */
+public enum ThemeComponent {
+ UNKNOWN(-1),
+ OVERLAY(0),
+ BOOT_ANIM(1),
+ WALLPAPER(2),
+ LOCKSCREEN(3),
+ FONT(4),
+ ICON(5),
+ SOUND(6);
+
+ public int id;
+ ThemeComponent(int id) {
+ this.id = id;
+ }
+
+}
diff --git a/sdk/src/java/cyanogenmod/app/ThemeVersion.java b/sdk/src/java/cyanogenmod/app/ThemeVersion.java
new file mode 100644
index 0000000..b9846c6
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ThemeVersion.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2015 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 cyanogenmod.app;
+
+import android.os.Build;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class ThemeVersion {
+ private static final String THEME_VERSION_CLASS_NAME = "android.content.ThemeVersion";
+ private static final String THEME_VERSION_FIELD_NAME = "THEME_VERSION";
+ private static final String MIN_SUPPORTED_THEME_VERSION_FIELD_NAME =
+ "MIN_SUPPORTED_THEME_VERSION";
+ private static final int CM11 = 1;
+ private static final int CM12_PRE_VERSIONING = 2;
+
+ public static int getVersion() {
+ int version;
+ try {
+ Class<?> themeVersionClass = Class.forName(THEME_VERSION_CLASS_NAME);
+ Field themeVersionField = themeVersionClass.getField(THEME_VERSION_FIELD_NAME);
+ version = (Integer) themeVersionField.get(null);
+ } catch(Exception e) {
+ // Field doesn't exist. Fallback to SDK level
+ version = Build.VERSION.SDK_INT < 21 ? CM11 :
+ CM12_PRE_VERSIONING;
+ }
+ return version;
+ }
+
+ public static int getMinSupportedVersion() {
+ int getMinSupportedVersion;
+ try {
+ Class<?> themeVersionClass = Class.forName(THEME_VERSION_CLASS_NAME);
+ Field themeVersionField =
+ themeVersionClass.getField(MIN_SUPPORTED_THEME_VERSION_FIELD_NAME);
+ getMinSupportedVersion = (Integer) themeVersionField.get(null);
+ } catch(Exception e) {
+ // Field doesn't exist. Fallback to SDK level
+ getMinSupportedVersion = Build.VERSION.SDK_INT < 21 ? CM11 :
+ CM12_PRE_VERSIONING;
+ }
+ return getMinSupportedVersion;
+ }
+
+ public static ComponentVersion getComponentVersion(ThemeComponent component) {
+ int version = getVersion();
+ if (version == 1) {
+ throw new UnsupportedOperationException();
+ } else if (version == 2) {
+ return ThemeVersionImpl2.getDeviceComponentVersion(component);
+ } else {
+ return ThemeVersionImpl3.getDeviceComponentVersion(component);
+ }
+ }
+
+ public static List<ComponentVersion> getComponentVersions() {
+ int version = getVersion();
+ if (version == 1) {
+ throw new UnsupportedOperationException();
+ } else if (version == 2) {
+ return ThemeVersionImpl2.getDeviceComponentVersions();
+ } else {
+ return ThemeVersionImpl3.getDeviceComponentVersions();
+ }
+ }
+
+ public static class ComponentVersion {
+ protected int id;
+ protected String name;
+ protected ThemeComponent component;
+ protected int minVersion;
+ protected int currentVersion;
+
+ protected ComponentVersion(int id, ThemeComponent component, int targetVersion) {
+ this(id, component, component.name(), targetVersion, targetVersion);
+ }
+
+ protected ComponentVersion(int id,
+ ThemeComponent component,
+ String name,
+ int minVersion,
+ int targetVersion) {
+ this.id = id;
+ this.component = component;
+ this.name = name;
+ this.minVersion = minVersion;
+ this.currentVersion = targetVersion;
+ }
+
+ public ComponentVersion(ComponentVersion copy) {
+ this(copy.id, copy.component, copy.name, copy.minVersion, copy.currentVersion);
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ThemeComponent getComponent() {
+ return component;
+ }
+
+ public int getMinVersion() {
+ return minVersion;
+ }
+
+ public int getCurrentVersion() {
+ return currentVersion;
+ }
+ }
+
+ private static class ThemeVersionImpl2 {
+ private static ArrayList<ComponentVersion> cVersions = new ArrayList<ComponentVersion>() {
+ {
+ add(new ComponentVersion(0, ThemeComponent.OVERLAY, 2));
+ add(new ComponentVersion(1, ThemeComponent.BOOT_ANIM, 1));
+ add(new ComponentVersion(2, ThemeComponent.WALLPAPER, 1));
+ add(new ComponentVersion(3, ThemeComponent.LOCKSCREEN, 1));
+ add(new ComponentVersion(4, ThemeComponent.ICON, 1));
+ add(new ComponentVersion(5, ThemeComponent.FONT, 1));
+ add(new ComponentVersion(6, ThemeComponent.SOUND, 1));
+ }
+ };
+
+ public static ComponentVersion getDeviceComponentVersion(ThemeComponent component) {
+ for(ComponentVersion compVersion : cVersions) {
+ if (compVersion.component.equals(component)) {
+ return new ComponentVersion(compVersion);
+ }
+ }
+ return null;
+ }
+
+ public static List<ComponentVersion> getDeviceComponentVersions() {
+ ArrayList<ComponentVersion> versions = new ArrayList<ComponentVersion>();
+ versions.addAll(cVersions);
+ return versions;
+ }
+ }
+
+ private static class ThemeVersionImpl3 {
+ public static ComponentVersion getDeviceComponentVersion(ThemeComponent component) {
+ for(android.content.ThemeVersion.ComponentVersion version :
+ android.content.ThemeVersion.ComponentVersion.values()) {
+ ComponentVersion sdkVersionInfo = fwCompVersionToSdkVersion(version);
+ if (sdkVersionInfo.component.equals(component)) {
+ return sdkVersionInfo;
+ }
+ }
+ return null;
+ }
+
+ public static List<ComponentVersion> getDeviceComponentVersions() {
+ List<ComponentVersion> versions = new ArrayList<ComponentVersion>();
+
+ for(android.content.ThemeVersion.ComponentVersion version :
+ android.content.ThemeVersion.ComponentVersion.values()) {
+ versions.add(fwCompVersionToSdkVersion(version));
+ }
+
+ return versions;
+ }
+
+ public static ComponentVersion fwCompVersionToSdkVersion(
+ android.content.ThemeVersion.ComponentVersion version) {
+ // Find the SDK component with the matching id
+ // If no ID matches then the FW must have a newer component that we don't
+ // know anything about. We can still return the id and name
+ ThemeComponent component = ThemeComponent.UNKNOWN;
+ for(ThemeComponent aComponent : ThemeComponent.values()) {
+ if (aComponent.id == version.id) {
+ component = aComponent;
+ }
+ }
+
+ int id = version.id;
+ String name = version.name();
+ int minVersion = version.minSupportedVersion;
+ int targetVersion = version.currentVersion;
+
+ return new ComponentVersion(id, component, name, minVersion, targetVersion);
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/suggest/AppSuggestManager.java b/sdk/src/java/cyanogenmod/app/suggest/AppSuggestManager.java
new file mode 100644
index 0000000..667eaa7
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/suggest/AppSuggestManager.java
@@ -0,0 +1,150 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app.suggest;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import cyanogenmod.app.CMContextConstants;
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+
+/**
+ * Provides an interface to get information about suggested apps for an intent which may include
+ * applications not installed on the device. This is used by the CMResolver in order to provide
+ * suggestions when an intent is fired but no application exists for the given intent.
+ *
+ * @hide
+ */
+public class AppSuggestManager {
+ private static final String TAG = AppSuggestManager.class.getSimpleName();
+ private static final boolean DEBUG = true;
+
+ private static IAppSuggestManager sImpl;
+
+ private static AppSuggestManager sInstance;
+
+ private Context mContext;
+
+ /**
+ * Gets an instance of the AppSuggestManager.
+ *
+ * @param context
+ *
+ * @return An instance of the AppSuggestManager
+ */
+ public static synchronized AppSuggestManager getInstance(Context context) {
+ if (sInstance != null) {
+ return sInstance;
+ }
+
+ context = context.getApplicationContext() != null ?
+ context.getApplicationContext() : context;
+
+ sInstance = new AppSuggestManager(context);
+
+ if (context.getPackageManager().hasSystemFeature(CMContextConstants.Features.APP_SUGGEST)
+ && sImpl == null) {
+ throw new RuntimeException("Unable to get AppSuggestManagerService. " +
+ "The service either crashed, was not started, or the interface has been" +
+ " called to early in SystemServer init");
+ }
+
+ return sInstance;
+ }
+
+ private AppSuggestManager(Context context) {
+ mContext = context.getApplicationContext();
+ sImpl = getService();
+ }
+
+ /** @hide */
+ public static synchronized IAppSuggestManager getService() {
+ if (sImpl == null) {
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_APP_SUGGEST_SERVICE);
+ if (b != null) {
+ sImpl = IAppSuggestManager.Stub.asInterface(b);
+ } else {
+ Log.e(TAG, "Unable to find implementation for app suggest service");
+ }
+ }
+
+ return sImpl;
+ }
+
+ /**
+ * Checks to see if an intent is handled by the App Suggestions Service. This should be
+ * implemented in such a way that it is safe to call inline on the UI Thread.
+ *
+ * @param intent The intent
+ * @return true if the App Suggestions Service has suggestions for this intent, false otherwise
+ */
+ public boolean handles(Intent intent) {
+ IAppSuggestManager mgr = getService();
+ if (mgr == null) return false;
+ try {
+ return mgr.handles(intent);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ *
+ * Gets a list of the suggestions for the given intent.
+ *
+ * @param intent The intent
+ * @return A list of application suggestions or an empty list if none.
+ */
+ public List<ApplicationSuggestion> getSuggestions(Intent intent) {
+ IAppSuggestManager mgr = getService();
+ if (mgr == null) return new ArrayList<>(0);
+ try {
+ return mgr.getSuggestions(intent);
+ } catch (RemoteException e) {
+ return new ArrayList<>(0);
+ }
+ }
+
+ /**
+ * Loads the icon for the given suggestion.
+ *
+ * @param suggestion The suggestion to load the icon for
+ *
+ * @return A {@link Drawable} or null if one cannot be found
+ */
+ public Drawable loadIcon(ApplicationSuggestion suggestion) {
+ try {
+ InputStream is = mContext.getContentResolver()
+ .openInputStream(suggestion.getThumbailUri());
+ return Drawable.createFromStream(is, null);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.aidl b/sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.aidl
new file mode 100644
index 0000000..7ab8584
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.aidl
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app.suggest;
+
+/**
+ * @hide
+ */
+parcelable ApplicationSuggestion;
diff --git a/sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.java b/sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.java
new file mode 100644
index 0000000..17e40b9
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.java
@@ -0,0 +1,110 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app.suggest;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+/**
+ * @hide
+ */
+public class ApplicationSuggestion implements Parcelable {
+
+ public static final Creator<ApplicationSuggestion> CREATOR =
+ new Creator<ApplicationSuggestion>() {
+ public ApplicationSuggestion createFromParcel(Parcel in) {
+ return new ApplicationSuggestion(in);
+ }
+
+ public ApplicationSuggestion[] newArray(int size) {
+ return new ApplicationSuggestion[size];
+ }
+ };
+
+ private String mName;
+
+ private String mPackage;
+
+ private Uri mDownloadUri;
+
+ private Uri mThumbnailUri;
+
+ public ApplicationSuggestion(@NonNull String name, @NonNull String pkg,
+ @NonNull Uri downloadUri, @NonNull Uri thumbnailUri) {
+ mName = name;
+ mPackage = pkg;
+ mDownloadUri = downloadUri;
+ mThumbnailUri = thumbnailUri;
+ }
+
+ private ApplicationSuggestion(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+ mName = in.readString();
+ mPackage = in.readString();
+ mDownloadUri = in.readParcelable(Uri.class.getClassLoader());
+ mThumbnailUri = in.readParcelable(Uri.class.getClassLoader());
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(out);
+
+ out.writeString(mName);
+ out.writeString(mPackage);
+ out.writeParcelable(mDownloadUri, flags);
+ out.writeParcelable(mThumbnailUri, flags);
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getPackageName() {
+ return mPackage;
+ }
+
+ public Uri getDownloadUri() {
+ return mDownloadUri;
+ }
+
+ public Uri getThumbailUri() {
+ return mThumbnailUri;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/suggest/IAppSuggestManager.aidl b/sdk/src/java/cyanogenmod/app/suggest/IAppSuggestManager.aidl
new file mode 100644
index 0000000..68ab87f
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/suggest/IAppSuggestManager.aidl
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app.suggest;
+
+import android.content.Intent;
+
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+
+/**
+ * @hide
+ */
+interface IAppSuggestManager {
+ boolean handles(in Intent intent);
+
+ List<ApplicationSuggestion> getSuggestions(in Intent intent);
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/app/suggest/IAppSuggestProvider.aidl b/sdk/src/java/cyanogenmod/app/suggest/IAppSuggestProvider.aidl
new file mode 100644
index 0000000..759880d
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/suggest/IAppSuggestProvider.aidl
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2015, 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 cyanogenmod.app.suggest;
+
+import android.content.Intent;
+
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+
+/**
+ * @hide
+ */
+interface IAppSuggestProvider {
+ boolean handles(in Intent intent);
+
+ List<ApplicationSuggestion> getSuggestions(in Intent intent);
+} \ No newline at end of file