diff options
author | Adnan Begovic <adnan@cyngn.com> | 2016-04-01 12:21:24 -0700 |
---|---|---|
committer | Adnan Begovic <adnan@cyngn.com> | 2016-04-01 12:22:14 -0700 |
commit | 5f6c9f40c7bb8c442849fe5cd64305fe4b7c3f9f (patch) | |
tree | 386106bf2d1a2eb1cd18b90a2b6396f2997980e4 /sdk/src/java/cyanogenmod | |
parent | 1ee5f204cc7a8b2ce869897c28145b38cc06e629 (diff) | |
download | vendor_cmsdk-5f6c9f40c7bb8c442849fe5cd64305fe4b7c3f9f.zip vendor_cmsdk-5f6c9f40c7bb8c442849fe5cd64305fe4b7c3f9f.tar.gz vendor_cmsdk-5f6c9f40c7bb8c442849fe5cd64305fe4b7c3f9f.tar.bz2 |
cmsdk: Move sdk classes under new sdk directory.
TICKET: CYNGNOS-2299
Change-Id: Ia6c6a1ee901f4f94446c379cbceabfdfced651ef
Diffstat (limited to 'sdk/src/java/cyanogenmod')
90 files changed, 20322 insertions, 0 deletions
diff --git a/sdk/src/java/cyanogenmod/alarmclock/ClockContract.java b/sdk/src/java/cyanogenmod/alarmclock/ClockContract.java new file mode 100644 index 0000000..63a1d27 --- /dev/null +++ b/sdk/src/java/cyanogenmod/alarmclock/ClockContract.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cyanogenmod.alarmclock; + +import android.net.Uri; +import android.provider.BaseColumns; + +/** + * <p> + * The contract between the clock provider and desk clock. Contains + * definitions for the supported URIs and data columns. + * </p> + * <h3>Overview</h3> + * <p> + * ClockContract defines the data model of clock related information. + * This data is stored in a number of tables: + * </p> + * <ul> + * <li>The {@link AlarmsColumns} table holds the user created alarms</li> + * <li>The {@link InstancesColumns} table holds the current state of each + * alarm in the AlarmsColumn table. + * </li> + * <li>The {@link CitiesColumns} table holds all user selectable cities</li> + * </ul> + * + * <p> + * Requires {@link cyanogenmod.alarmclock.CyanogenModAlarmClock#READ_ALARMS_PERMISSION} + * to read from the provider. + * Requires {@link cyanogenmod.alarmclock.CyanogenModAlarmClock#WRITE_ALARMS_PERMISSION} to write + * to the provider. + * </p> + */ +public final class ClockContract { + /** + * This authority is used for writing to or querying from the clock + * provider. + */ + public static final String AUTHORITY = "com.android.deskclock"; + + /** + * This utility class cannot be instantiated + */ + private ClockContract() {} + + /** + * Constants for tables with AlarmSettings. + */ + public interface AlarmSettingColumns extends BaseColumns { + /** + * This string is used to indicate no ringtone. + */ + public static final Uri NO_RINGTONE_URI = Uri.EMPTY; + + /** + * This string is used to indicate no ringtone. + */ + public static final String NO_RINGTONE = NO_RINGTONE_URI.toString(); + + /** + * True if alarm should vibrate + * <p>Type: BOOLEAN</p> + */ + public static final String VIBRATE = "vibrate"; + + /** + * Alarm label. + * + * <p>Type: STRING</p> + */ + public static final String LABEL = "label"; + + /** + * Audio alert to play when alarm triggers. Null entry + * means use system default and entry that equal + * Uri.EMPTY.toString() means no ringtone. + * + * <p>Type: STRING</p> + */ + public static final String RINGTONE = "ringtone"; + + /** + * True if alarm should start off quiet and slowly increase volume + * <P>Type: BOOLEAN</P> + */ + public static final String INCREASING_VOLUME = "incvol"; + + /** + * Profile to change to when alarm triggers + * <P>Type: STRING</P> + */ + public static final String PROFILE = "profile"; + } + + /** + * Constants for the Alarms table, which contains the user created alarms. + */ + public interface AlarmsColumns extends AlarmSettingColumns, BaseColumns { + /** + * The content:// style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/alarms"); + + /** + * Hour in 24-hour localtime 0 - 23. + * <p>Type: INTEGER</p> + */ + public static final String HOUR = "hour"; + + /** + * Minutes in localtime 0 - 59. + * <p>Type: INTEGER</p> + */ + public static final String MINUTES = "minutes"; + + /** + * Days of the week encoded as a bit set. + * <p>Type: INTEGER</p> + * + */ + public static final String DAYS_OF_WEEK = "daysofweek"; + + /** + * True if alarm is active. + * <p>Type: BOOLEAN</p> + */ + public static final String ENABLED = "enabled"; + + /** + * Determine if alarm is deleted after it has been used. + * <p>Type: INTEGER</p> + */ + public static final String DELETE_AFTER_USE = "delete_after_use"; + } + + /** + * Constants for the Instance table, which contains the state of each alarm. + */ + public interface InstancesColumns extends AlarmSettingColumns, BaseColumns { + /** + * The content:// style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/instances"); + + + /** + * Alarm state for rtc power off alarm + */ + public static final int POWER_OFF_ALARM_STATE = -1; + + /** + * Alarm state when to show no notification. + * + * Can transitions to: + * LOW_NOTIFICATION_STATE + */ + public static final int SILENT_STATE = 0; + + /** + * Alarm state to show low priority alarm notification. + * + * Can transitions to: + * HIDE_NOTIFICATION_STATE + * HIGH_NOTIFICATION_STATE + * DISMISSED_STATE + */ + public static final int LOW_NOTIFICATION_STATE = 1; + + /** + * Alarm state to hide low priority alarm notification. + * + * Can transitions to: + * HIGH_NOTIFICATION_STATE + */ + public static final int HIDE_NOTIFICATION_STATE = 2; + + /** + * Alarm state to show high priority alarm notification. + * + * Can transitions to: + * DISMISSED_STATE + * FIRED_STATE + */ + public static final int HIGH_NOTIFICATION_STATE = 3; + + /** + * Alarm state when alarm is in snooze. + * + * Can transitions to: + * DISMISSED_STATE + * FIRED_STATE + */ + public static final int SNOOZE_STATE = 4; + + /** + * Alarm state when alarm is being fired. + * + * Can transitions to: + * DISMISSED_STATE + * SNOOZED_STATE + * MISSED_STATE + */ + public static final int FIRED_STATE = 5; + + /** + * Alarm state when alarm has been missed. + * + * Can transitions to: + * DISMISSED_STATE + */ + public static final int MISSED_STATE = 6; + + /** + * Alarm state when alarm is done. + */ + public static final int DISMISSED_STATE = 7; + + /** + * Alarm state when alarm has been dismissed before its intended firing time. + */ + public static final int PREDISMISSED_STATE = 8; + + /** + * Alarm year. + * + * <p>Type: INTEGER</p> + */ + public static final String YEAR = "year"; + + /** + * Alarm month in year. + * + * <p>Type: INTEGER</p> + */ + public static final String MONTH = "month"; + + /** + * Alarm day in month. + * + * <p>Type: INTEGER</p> + */ + public static final String DAY = "day"; + + /** + * Alarm hour in 24-hour localtime 0 - 23. + * <p>Type: INTEGER</p> + */ + public static final String HOUR = "hour"; + + /** + * Alarm minutes in localtime 0 - 59 + * <p>Type: INTEGER</p> + */ + public static final String MINUTES = "minutes"; + + /** + * Foreign key to Alarms table + * <p>Type: INTEGER (long)</p> + */ + public static final String ALARM_ID = "alarm_id"; + + /** + * Alarm state + * <p>Type: INTEGER</p> + */ + public static final String ALARM_STATE = "alarm_state"; + } + + /** + * Constants for the Cities table, which contains all selectable cities. + */ + public interface CitiesColumns { + /** + * The content:// style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/cities"); + + /** + * Primary id for city. + * <p>Type: STRING</p> + */ + public static final String CITY_ID = "city_id"; + + /** + * City name. + * <p>Type: STRING</p> + */ + public static final String CITY_NAME = "city_name"; + + /** + * Timezone name of city. + * <p>Type: STRING</p> + */ + public static final String TIMEZONE_NAME = "timezone_name"; + + /** + * Timezone offset. + * <p>Type: INTEGER</p> + */ + public static final String TIMEZONE_OFFSET = "timezone_offset"; + } +} diff --git a/sdk/src/java/cyanogenmod/alarmclock/CyanogenModAlarmClock.java b/sdk/src/java/cyanogenmod/alarmclock/CyanogenModAlarmClock.java new file mode 100644 index 0000000..76508a9 --- /dev/null +++ b/sdk/src/java/cyanogenmod/alarmclock/CyanogenModAlarmClock.java @@ -0,0 +1,163 @@ +/** + * 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.alarmclock; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.provider.AlarmClock; + +import java.util.List; + +/** + * The CyanogenModAlarmClock class contains utilities for interacting with + * a variety of Alarm features that the CyanogenMod AlarmClock application + * (based on AOSP DeskClock) supports. + */ +public class CyanogenModAlarmClock { + /** + * The package name of the CyanogenMod DeskClock application. + */ + private static final String DESKCLOCK_PACKAGE = "com.android.deskclock"; + + /** + * Allows an application to make modifications to existing alarms, + * such as turning them on or off. + * + * @see #ACTION_SET_ALARM_ENABLED + */ + public static final String MODIFY_ALARMS_PERMISSION + = "cyanogenmod.alarmclock.permission.MODIFY_ALARMS"; + + /** + * Allows an application to have read access to all alarms in the + * CyanogenMod DeskClock application. + * + * @see cyanogenmod.alarmclock.ClockContract + */ + public static final String READ_ALARMS_PERMISSION + = "cyanogenmod.alarmclock.permission.READ_ALARMS"; + + /** + * Allows an application to have write access to all alarms in the + * CyanogenMod DeskClock application. This is a system level permission. + * + * @see cyanogenmod.alarmclock.ClockContract + * @hide + */ + public static final String WRITE_ALARMS_PERMISSION + = "cyanogenmod.alarmclock.permission.WRITE_ALARMS"; + + /** + * Service Action: Set an existing alarm to be either enabled or disabled. + * <p> + * This action sets an alarm to be enabled or disabled. + * </p><p> + * This action requests an alarm with the id specified by {@link #EXTRA_ALARM_ID} + * be set to enabled or disabled, depending on the value set with {@link #EXTRA_ENABLED}. + * </p> + * + * <p>Requires permission {@link #MODIFY_ALARMS_PERMISSION} to launch this + * intent. + * </p> + * + * <p>Always set the package name of the Intent that will launch this action + * to {@link #DESKCLOCK_PACKAGE} explicitly, for security.</p> + * + * <h3>Request parameters</h3> + * <ul> + * <li>{@link #EXTRA_ALARM_ID} <em>(required)</em>: The id of the alarm to modify, + * as stored in {@link cyanogenmod.alarmclock.ClockContract.AlarmsColumns#_ID}</li> + * <li>{@link #EXTRA_ENABLED} <em>(required)</em>: Whether to set this alarm to be enabled + * or disabled. </li> + * </ul> + */ + public static final String ACTION_SET_ALARM_ENABLED + = "cyanogenmod.alarmclock.SET_ALARM_ENABLED"; + + /** + * Bundle extra: The id of the alarm. + * <p> + * Used by {@link #ACTION_SET_ALARM_ENABLED}. + * </p><p> + * This extra is required. + * </p><p> + * The value is an {@link Long} and is the ID stored in + * {@link cyanogenmod.alarmclock.ClockContract.AlarmsColumns#_ID} for this alarm. + * </p> + * + * @see #ACTION_SET_ALARM_ENABLED + * @see #EXTRA_ENABLED + */ + public static final String EXTRA_ALARM_ID = "cyanogenmod.intent.extra.alarmclock.ID"; + + /** + * Bundle extra: Whether to set the alarm to enabled to disabled. + * <p> + * Used by {@link #ACTION_SET_ALARM_ENABLED}. + * </p><p> + * This extra is required. + * </p><p> + * The value is an {@link Boolean} and if true, will set the alarm specified by + * {@link #EXTRA_ALARM_ID} to be enabled. Otherwise, the alarm will be disabled. + * </p> + * + * @see #ACTION_SET_ALARM_ENABLED + * @see #EXTRA_ALARM_ID + */ + public static final String EXTRA_ENABLED = "cyanogenmod.intent.extra.alarmclock.ENABLED"; + + /** + * <p> + * Retrieves an Intent that is prepopulated with the proper action and ComponentName to + * create a new alarm in the CyanogenMod DeskClock application. + * </p> + * <p> The action will be set to {@link android.provider.AlarmClock#ACTION_SET_ALARM}. Use the + * Intent extras contained at {@link android.provider.AlarmClock} to configure the alarm. + * </p> + * <p>Requires permission {@link android.Manifest.permission#SET_ALARM} to launch this + * intent. + * </p> + * + * @see android.provider.AlarmClock#ACTION_SET_ALARM + * @return The Intent to create a new alarm with the CyanogenMod DeskClock application. + */ + public static Intent createAlarmIntent(Context context) { + Intent intent = new Intent(); + intent.setAction(AlarmClock.ACTION_SET_ALARM); + + // Retrieve the ComponentName of the best result + // for ACTION_SET_ALARM within system applications only. + // This will exclude third party alarm apps that have been installed. + PackageManager pm = context.getPackageManager(); + List<ResolveInfo> resolves = pm.queryIntentActivities(intent, 0); + ComponentName selectedSystemComponent = null; + for (ResolveInfo info : resolves) { + if ((info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + selectedSystemComponent = new ComponentName(info.activityInfo.packageName, + info.activityInfo.name); + break; + } + } + if (selectedSystemComponent != null) { + intent.setComponent(selectedSystemComponent); + } + return intent; + } +} 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> + * <service android:name=".CustomTileListener" + * android:label="@string/service_name" + * android:permission="cyanogenmod.permission.BIND_CUSTOM_TILE_LISTENER_SERVICE"> + * <intent-filter> + * <action android:name="cyanogenmod.app.CustomTileListenerService" /> + * </intent-filter> + * </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 diff --git a/sdk/src/java/cyanogenmod/content/Intent.java b/sdk/src/java/cyanogenmod/content/Intent.java new file mode 100644 index 0000000..6369b7c --- /dev/null +++ b/sdk/src/java/cyanogenmod/content/Intent.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. + * + * 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.content; + +import android.Manifest; + +/** + * CyanogenMod specific intent definition class. + */ +public class Intent { + + /** + * Activity Action: Start action associated with long press on the recents key. + * <p>Input: {@link #EXTRA_LONG_PRESS_RELEASE} is set to true if the long press + * is released + * <p>Output: Nothing + * @hide + */ + public static final String ACTION_RECENTS_LONG_PRESS = + "cyanogenmod.intent.action.RECENTS_LONG_PRESS"; + + /** + * This field is part of the intent {@link #ACTION_RECENTS_LONG_PRESS}. + * The type of the extra is a boolean that indicates if the long press + * is released. + * @hide + */ + public static final String EXTRA_RECENTS_LONG_PRESS_RELEASE = + "cyanogenmod.intent.extra.RECENTS_LONG_PRESS_RELEASE"; + + /** + * Intent filter to update protected app component's settings + */ + public static final String ACTION_PROTECTED = "cyanogenmod.intent.action.PACKAGE_PROTECTED"; + + /** + * Intent filter to notify change in state of protected application. + */ + public static final String ACTION_PROTECTED_CHANGED = + "cyanogenmod.intent.action.PROTECTED_COMPONENT_UPDATE"; + + /** + * This field is part of the intent {@link #ACTION_PROTECTED_CHANGED}. + * Intent extra field for the state of protected application + */ + public static final String EXTRA_PROTECTED_STATE = + "cyanogenmod.intent.extra.PACKAGE_PROTECTED_STATE"; + + /** + * This field is part of the intent {@link #ACTION_PROTECTED_CHANGED}. + * Intent extra field to indicate protected component value + */ + public static final String EXTRA_PROTECTED_COMPONENTS = + "cyanogenmod.intent.extra.PACKAGE_PROTECTED_COMPONENTS"; + + /** + * Broadcast action: notify the system that the user has performed a gesture on the screen + * to launch the camera. Broadcast should be protected to receivers holding the + * {@link Manifest.permission#STATUS_BAR_SERVICE} permission. + * @hide + */ + public static final String ACTION_SCREEN_CAMERA_GESTURE = + "cyanogenmod.intent.action.SCREEN_CAMERA_GESTURE"; + + /** + * Broadcast action: perform any initialization required for CMHW services. + * Runs when the service receives the signal the device has booted, but + * should happen before {@link android.content.Intent#ACTION_BOOT_COMPLETED}. + * + * Requires {@link cyanogenmod.platform.Manifest.permission#HARDWARE_ABSTRACTION_ACCESS}. + * @hide + */ + public static final String ACTION_INITIALIZE_CM_HARDWARE = + "cyanogenmod.intent.action.INITIALIZE_CM_HARDWARE"; + + /** + * Broadcast Action: Indicate that an unrecoverable error happened during app launch. + * Could indicate that curently applied theme is malicious. + * @hide + */ + public static final String ACTION_APP_FAILURE = "cyanogenmod.intent.action.APP_FAILURE"; + + /** + * Used to indicate that a theme package has been installed or un-installed. + */ + public static final String CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE = + "cyanogenmod.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE"; + + /** + * Action sent from the provider when a theme has been fully installed. Fully installed + * means that the apk was installed by PackageManager and the theme resources were + * processed and cached by {@link org.cyanogenmod.platform.internal.ThemeManagerService} + * Requires the {@link cyanogenmod.platform.Manifest.permission#READ_THEMES} permission to + * receive this broadcast. + */ + public static final String ACTION_THEME_INSTALLED = + "cyanogenmod.intent.action.THEME_INSTALLED"; + + /** + * Action sent from the provider when a theme has been updated. + * Requires the {@link cyanogenmod.platform.Manifest.permission#READ_THEMES} permission to + * receive this broadcast. + */ + public static final String ACTION_THEME_UPDATED = + "cyanogenmod.intent.action.THEME_UPDATED"; + + /** + * Action sent from the provider when a theme has been removed. + * Requires the {@link cyanogenmod.platform.Manifest.permission#READ_THEMES} permission to + * receive this broadcast. + */ + public static final String ACTION_THEME_REMOVED = + "cyanogenmod.intent.action.THEME_REMOVED"; + + /** + * Uri scheme used to broadcast the theme's package name when broadcasting + * {@link Intent#ACTION_THEME_INSTALLED} or + * {@link Intent#ACTION_THEME_REMOVED} + */ + public static final String URI_SCHEME_PACKAGE = "package"; + + /** + * Implicit action to open live lock screen settings. + * @hide + */ + public static final String ACTION_OPEN_LIVE_LOCKSCREEN_SETTINGS = + "cyanogenmod.intent.action.OPEN_LIVE_LOCKSCREEN_SETTINGS"; + +} diff --git a/sdk/src/java/cyanogenmod/externalviews/ExternalView.java b/sdk/src/java/cyanogenmod/externalviews/ExternalView.java new file mode 100644 index 0000000..3c3adb4 --- /dev/null +++ b/sdk/src/java/cyanogenmod/externalviews/ExternalView.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.externalviews; + +import android.app.Activity; +import android.app.Application; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewTreeObserver; + +import java.util.LinkedList; + +/** + * TODO: unhide once documented and finalized + * @hide + */ +public class ExternalView extends View implements Application.ActivityLifecycleCallbacks, + ViewTreeObserver.OnPreDrawListener { + + private LinkedList<Runnable> mQueue = new LinkedList<Runnable>(); + + protected Context mContext; + protected final ExternalViewProperties mExternalViewProperties; + protected volatile IExternalViewProvider mExternalViewProvider; + + public ExternalView(Context context, AttributeSet attrs) { + this(context, attrs, null); + } + + public ExternalView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs); + } + + public ExternalView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + this(context, attrs); + } + + public ExternalView(Context context, AttributeSet attributeSet, ComponentName componentName) { + super(context, attributeSet); + mContext = getContext(); + mExternalViewProperties = new ExternalViewProperties(this, mContext); + Application app = (mContext instanceof Activity) ? ((Activity) mContext).getApplication() + : (Application) mContext; + app.registerActivityLifecycleCallbacks(this); + if (componentName != null) { + mContext.bindService(new Intent().setComponent(componentName), + mServiceConnection, Context.BIND_AUTO_CREATE); + } + } + + private ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + try { + mExternalViewProvider = IExternalViewProvider.Stub.asInterface( + IExternalViewProviderFactory.Stub.asInterface(service).createExternalView(null)); + executeQueue(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mExternalViewProvider = null; + } + }; + + private void executeQueue() { + while (!mQueue.isEmpty()) { + Runnable r = mQueue.pop(); + r.run(); + } + } + + protected void performAction(Runnable r) { + if (mExternalViewProvider != null) { + r.run(); + } else { + mQueue.add(r); + } + } + + // view overrides, for positioning + + @Override + public boolean onPreDraw() { + long cur = System.currentTimeMillis(); + if (!mExternalViewProperties.hasChanged()) { + return true; + } + final int x = mExternalViewProperties.getX(); + final int y = mExternalViewProperties.getY(); + final int width = mExternalViewProperties.getWidth(); + final int height = mExternalViewProperties.getHeight(); + final boolean visible = mExternalViewProperties.isVisible(); + final Rect clipRect = mExternalViewProperties.getHitRect(); + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.alterWindow(x, y, width, height, visible, + clipRect); + } catch (RemoteException e) { + } + } + }); + return true; + } + + // Activity lifecycle callbacks + + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + } + + @Override + public void onActivityStarted(Activity activity) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onStart(); + } catch (RemoteException e) { + } + } + }); + } + + @Override + public void onActivityResumed(Activity activity) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onResume(); + } catch (RemoteException e) { + } + getViewTreeObserver().addOnPreDrawListener(ExternalView.this); + } + }); + } + + @Override + public void onActivityPaused(Activity activity) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onPause(); + } catch (RemoteException e) { + } + getViewTreeObserver().removeOnPreDrawListener(ExternalView.this); + } + }); + } + + @Override + public void onActivityStopped(Activity activity) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onStop(); + } catch (RemoteException e) { + } + } + }); + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + } + + @Override + public void onActivityDestroyed(Activity activity) { + mExternalViewProvider = null; + mContext.unbindService(mServiceConnection); + } + + // Placeholder callbacks + + @Override + public void onDetachedFromWindow() { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onDetach(); + } catch (RemoteException e) { + } + } + }); + } + + @Override + public void onAttachedToWindow() { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onAttach(null); + } catch (RemoteException e) { + } + } + }); + } + + /** + * Sets the component of the ExternalViewProviderService to be used for this ExternalView. + * If a provider is already connected to this view, it is first unbound before binding to the + * new provider. + * @param componentName + */ + public void setProviderComponent(ComponentName componentName) { + // unbind any existing external view provider + if (mExternalViewProvider != null) { + mContext.unbindService(mServiceConnection); + } + if (componentName != null) { + mContext.bindService(new Intent().setComponent(componentName), + mServiceConnection, Context.BIND_AUTO_CREATE); + } + } +} diff --git a/sdk/src/java/cyanogenmod/externalviews/ExternalViewProperties.java b/sdk/src/java/cyanogenmod/externalviews/ExternalViewProperties.java new file mode 100644 index 0000000..2b4404d --- /dev/null +++ b/sdk/src/java/cyanogenmod/externalviews/ExternalViewProperties.java @@ -0,0 +1,95 @@ +/* + * 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.externalviews; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.graphics.Rect; +import android.view.View; + +/** + * TODO: unhide once documented and finalized + * @hide + */ +public class ExternalViewProperties { + + private final int[] mScreenCoords = new int[2]; + private final View mView; + private final View mDecorView; + private int mWidth, mHeight; + private boolean mVisible; + private Rect mHitRect = new Rect(); + + ExternalViewProperties(View view, Context context) { + mView = view; + if (context instanceof Activity) { + mDecorView = ((Activity) context).getWindow().getDecorView(); + } else { + mDecorView = null; + } + } + + public Rect getHitRect() { + return mHitRect; + } + + public int getX() { + return mScreenCoords[0]; + } + + public int getY() { + return mScreenCoords[1]; + } + + public int getWidth() { + return mWidth; + } + + public int getHeight() { + return mHeight; + } + + public boolean isVisible() { + return mVisible; + } + + public boolean hasChanged() { + int previousWidth = mWidth; + int previousHeight = mHeight; + mWidth = mView.getWidth(); + mHeight = mView.getHeight(); + + int previousX = mScreenCoords[0]; + int previousY = mScreenCoords[1]; + mView.getLocationOnScreen(mScreenCoords); + int newX = mScreenCoords[0]; + int newY = mScreenCoords[1]; + + mHitRect.setEmpty(); + if (mDecorView != null) { + mDecorView.getHitRect(mHitRect); + } + boolean visible = mView.getLocalVisibleRect(mHitRect); + mVisible = visible; + + // Check if anything actually changed + return previousX != newX || previousY != newY + || previousWidth != mWidth || previousHeight != mHeight + || mVisible != visible; + } +} diff --git a/sdk/src/java/cyanogenmod/externalviews/ExternalViewProviderService.java b/sdk/src/java/cyanogenmod/externalviews/ExternalViewProviderService.java new file mode 100644 index 0000000..b15e01a --- /dev/null +++ b/sdk/src/java/cyanogenmod/externalviews/ExternalViewProviderService.java @@ -0,0 +1,236 @@ +/* + * 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.externalviews; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; + +import com.android.internal.policy.PhoneWindow; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +/** + * TODO: unhide once documented and finalized + * @hide + */ +public abstract class ExternalViewProviderService extends Service { + + private static final String TAG = "ExternalViewProvider"; + private static final boolean DEBUG = false; + + private WindowManager mWindowManager; + private final Handler mHandler = new Handler(); + + @Override + public void onCreate() { + super.onCreate(); + + mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + } + + @Override + public final IBinder onBind(Intent intent) { + return new IExternalViewProviderFactory.Stub() { + @Override public IBinder createExternalView(final Bundle options) { + FutureTask<IBinder> c = new FutureTask<IBinder>(new Callable<IBinder>() { + @Override + public IBinder call() throws Exception { + return ExternalViewProviderService.this.createExternalView(options).mImpl; + } + }); + mHandler.post(c); + try { + return c.get(); + } catch (InterruptedException | ExecutionException e) { + Log.e(TAG, "error: ", e); + return null; + } + } + }; + } + + protected abstract Provider createExternalView(Bundle options); + + protected abstract class Provider { + public static final int DEFAULT_WINDOW_TYPE = WindowManager.LayoutParams.TYPE_PHONE; + public static final int DEFAULT_WINDOW_FLAGS = + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;; + + private final class ProviderImpl extends IExternalViewProvider.Stub { + private final Window mWindow; + private final WindowManager.LayoutParams mParams; + + private boolean mShouldShow = true; + private boolean mAskedShow = false; + + public ProviderImpl(Provider provider) { + mWindow = new PhoneWindow(ExternalViewProviderService.this); + ((ViewGroup) mWindow.getDecorView()).addView(onCreateView()); + + mParams = new WindowManager.LayoutParams(); + mParams.type = provider.getWindowType(); + mParams.flags = provider.getWindowFlags(); + mParams.gravity = Gravity.LEFT | Gravity.TOP; + mParams.format = PixelFormat.TRANSPARENT; + } + + @Override + public void onAttach(IBinder windowToken) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mWindowManager.addView(mWindow.getDecorView(), mParams); + Provider.this.onAttach(); + } + }); + } + + @Override + public void onStart() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onStart(); + } + }); + } + + @Override + public void onResume() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mShouldShow = true; + updateVisibility(); + Provider.this.onResume(); + } + }); + } + + @Override + public void onPause() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mShouldShow = false; + updateVisibility(); + Provider.this.onPause(); + } + }); + } + + @Override + public void onStop() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onStop(); + } + }); + } + + @Override + public void onDetach() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mWindowManager.removeView(mWindow.getDecorView()); + Provider.this.onDetach(); + } + }); + } + + @Override + public void alterWindow(final int x, final int y, final int width, final int height, + final boolean visible, final Rect clipRect) { + mHandler.post(new Runnable() { + @Override + public void run() { + mParams.x = x; + mParams.y = y; + mParams.width = width; + mParams.height = height; + + if (DEBUG) Log.d(TAG, mParams.toString()); + + mAskedShow = visible; + + updateVisibility(); + + View decorView = mWindow.getDecorView(); + if (decorView.getVisibility() == View.VISIBLE) { + decorView.setClipBounds(clipRect); + } + + if (mWindow.getDecorView().getVisibility() != View.GONE) + mWindowManager.updateViewLayout(mWindow.getDecorView(), mParams); + } + }); + } + + private void updateVisibility() { + if (DEBUG) Log.d(TAG, "shouldShow = " + mShouldShow + " askedShow = " + mAskedShow); + mWindow.getDecorView().setVisibility(mShouldShow && mAskedShow ? + View.VISIBLE : View.GONE); + } + } + + private final ProviderImpl mImpl = new ProviderImpl(this); + private final Bundle mOptions; + + protected Provider(Bundle options) { + mOptions = options; + } + + protected Bundle getOptions() { + return mOptions; + } + + protected void onAttach() {} + protected abstract View onCreateView(); + protected void onStart() {} + protected void onResume() {} + protected void onPause() {} + protected void onStop() {} + protected void onDetach() {} + + /*package*/ int getWindowType() { + return DEFAULT_WINDOW_TYPE; + } + + /*package*/ int getWindowFlags() { + return DEFAULT_WINDOW_FLAGS; + } + } +}
\ No newline at end of file diff --git a/sdk/src/java/cyanogenmod/externalviews/IExternalViewProvider.aidl b/sdk/src/java/cyanogenmod/externalviews/IExternalViewProvider.aidl new file mode 100644 index 0000000..e05215f --- /dev/null +++ b/sdk/src/java/cyanogenmod/externalviews/IExternalViewProvider.aidl @@ -0,0 +1,32 @@ +/** + * 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.externalviews; + +import android.graphics.Rect; + +/** @hide */ +interface IExternalViewProvider +{ + oneway void onAttach(in IBinder windowToken); + oneway void onStart(); + oneway void onResume(); + oneway void onPause(); + oneway void onStop(); + oneway void onDetach(); + + void alterWindow(in int x, in int y, in int width, in int height, in boolean visible, in Rect clipRect); +} diff --git a/sdk/src/java/cyanogenmod/externalviews/IExternalViewProviderFactory.aidl b/sdk/src/java/cyanogenmod/externalviews/IExternalViewProviderFactory.aidl new file mode 100644 index 0000000..75a8b35 --- /dev/null +++ b/sdk/src/java/cyanogenmod/externalviews/IExternalViewProviderFactory.aidl @@ -0,0 +1,25 @@ +/** + * 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.externalviews; + +import android.os.Bundle; + +/** @hide */ +interface IExternalViewProviderFactory +{ + IBinder createExternalView(in Bundle options); +} diff --git a/sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewCallbacks.aidl b/sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewCallbacks.aidl new file mode 100644 index 0000000..c9d75e2 --- /dev/null +++ b/sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewCallbacks.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.externalviews; + +import android.content.Intent; + +/** @hide */ +interface IKeyguardExternalViewCallbacks { + boolean requestDismiss(); + boolean requestDismissAndStartActivity(in Intent intent); + oneway void collapseNotificationPanel(); + oneway void setInteractivity(boolean isInteractive); + oneway void onAttachedToWindow(); + oneway void onDetachedFromWindow(); + oneway void slideLockscreenIn(); +} diff --git a/sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewProvider.aidl b/sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewProvider.aidl new file mode 100644 index 0000000..a708984 --- /dev/null +++ b/sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewProvider.aidl @@ -0,0 +1,42 @@ +/** + * 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.externalviews; + +import android.graphics.Rect; + +import cyanogenmod.externalviews.IKeyguardExternalViewCallbacks; + +/** @hide */ +interface IKeyguardExternalViewProvider +{ + oneway void onAttach(in IBinder windowToken); + oneway void onDetach(); + + // Keyguard specific interface + oneway void onKeyguardShowing(boolean screenOn); + oneway void onKeyguardDismissed(); + oneway void onBouncerShowing(boolean showing); + oneway void onScreenTurnedOn(); + oneway void onScreenTurnedOff(); + + oneway void registerCallback(in IKeyguardExternalViewCallbacks callback); + oneway void unregisterCallback(in IKeyguardExternalViewCallbacks callback); + + void alterWindow(in int x, in int y, in int width, in int height, in boolean visible, + in Rect clipRect); + oneway void onLockscreenSlideOffsetChanged(float swipeProgress); +} diff --git a/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalView.java b/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalView.java new file mode 100644 index 0000000..9f4059a --- /dev/null +++ b/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalView.java @@ -0,0 +1,493 @@ +/* + * 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.externalviews; + +import android.app.Activity; +import android.app.Application; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.WindowManager; + +import java.util.LinkedList; + +/** + * This class provides a placeholder view for hosting an external view, provided by a + * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService}, within the lock screen. + * + * <p>This class is intended to only be used within the SystemUi process.</p> + * @hide + */ +public class KeyguardExternalView extends View implements ViewTreeObserver.OnPreDrawListener, + IBinder.DeathRecipient { + private static final String TAG = KeyguardExternalView.class.getSimpleName(); + + /** + * An extra passed via an intent that provides a list of permissions that should be requested + * from the user. + */ + public static final String EXTRA_PERMISSION_LIST = "permissions_list"; + + /** + * Category defining an activity to call to request permissions that a + * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService} will need. Apps that + * provide a {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService} should + * check that they have the required permission before making any method calls that would + * require a dangerous permission to be granted. + */ + public static final String CATEGORY_KEYGUARD_GRANT_PERMISSION + = "org.cyanogenmod.intent.category.KEYGUARD_GRANT_PERMISSION"; + + private LinkedList<Runnable> mQueue = new LinkedList<Runnable>(); + + private Context mContext; + private final ExternalViewProperties mExternalViewProperties; + private volatile IKeyguardExternalViewProvider mExternalViewProvider; + private IBinder mService; + private final Point mDisplaySize; + private boolean mIsInteractive; + + private KeyguardExternalViewCallbacks mCallback; + + private OnWindowAttachmentChangedListener mWindowAttachmentListener; + + public KeyguardExternalView(Context context, AttributeSet attrs) { + this(context, attrs, null); + } + + public KeyguardExternalView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs); + } + + public KeyguardExternalView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + this(context, attrs); + } + + /** + * @param context + * @param attributeSet + * @param componentName The component name for the + * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService} + * that will be bound to create the external view. + */ + public KeyguardExternalView(Context context, AttributeSet attributeSet, ComponentName componentName) { + super(context, attributeSet); + mContext = getContext(); + mExternalViewProperties = new ExternalViewProperties(this, mContext); + if (componentName != null) { + mContext.bindService(new Intent().setComponent(componentName), + mServiceConnection, Context.BIND_AUTO_CREATE); + } + mDisplaySize = new Point(); + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getRealSize(mDisplaySize); + } + + private ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + try { + IExternalViewProviderFactory factory = IExternalViewProviderFactory.Stub.asInterface(service); + if (factory != null) { + mExternalViewProvider = IKeyguardExternalViewProvider.Stub.asInterface( + factory.createExternalView(null)); + if (mExternalViewProvider != null) { + mExternalViewProvider.registerCallback( + KeyguardExternalView.this.mKeyguardExternalViewCallbacks); + mService = service; + mService.linkToDeath(KeyguardExternalView.this, 0); + executeQueue(); + } else { + Log.e(TAG, "Unable to get external view provider"); + } + } else { + Log.e(TAG, "Unable to get external view provider factory"); + } + } catch (RemoteException e) { + e.printStackTrace(); + } + // We should unbind the service if we failed to connect to the provider + if (mService != service && service != null) { + mContext.unbindService(mServiceConnection); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (mExternalViewProvider != null) { + try { + mExternalViewProvider.unregisterCallback( + KeyguardExternalView.this.mKeyguardExternalViewCallbacks); + } catch (RemoteException e) { + } + mExternalViewProvider = null; + } + if (mService != null) { + mService.unlinkToDeath(KeyguardExternalView.this, 0); + mService = null; + } + } + }; + + private final IKeyguardExternalViewCallbacks mKeyguardExternalViewCallbacks = + new IKeyguardExternalViewCallbacks.Stub() { + @Override + public boolean requestDismiss() throws RemoteException { + if (mCallback != null) { + return mCallback.requestDismiss(); + } + + return false; + } + + @Override + public boolean requestDismissAndStartActivity(Intent intent) throws RemoteException { + if (mCallback != null) { + return mCallback.requestDismissAndStartActivity(intent); + } + + return false; + } + + @Override + public void collapseNotificationPanel() throws RemoteException { + if (mCallback != null) { + mCallback.collapseNotificationPanel(); + } + } + + @Override + public void setInteractivity(boolean isInteractive) { + mIsInteractive = isInteractive; + } + + @Override + public void onAttachedToWindow() { + if (mWindowAttachmentListener != null) { + mWindowAttachmentListener.onAttachedToWindow(); + } + } + + @Override + public void onDetachedFromWindow() { + if (mWindowAttachmentListener != null) { + mWindowAttachmentListener.onDetachedFromWindow(); + } + } + + @Override + public void slideLockscreenIn() { + if (mCallback != null) { + mCallback.slideLockscreenIn(); + } + } + }; + + private void executeQueue() { + while (!mQueue.isEmpty()) { + Runnable r = mQueue.pop(); + r.run(); + } + } + + protected void performAction(Runnable r) { + if (mExternalViewProvider != null) { + r.run(); + } else { + mQueue.add(r); + } + } + + // view overrides, for positioning + + @Override + public boolean onPreDraw() { + if (!mExternalViewProperties.hasChanged()) { + return true; + } + // keyguard views always take up the full screen when visible + final int x = mExternalViewProperties.getX(); + final int y = mExternalViewProperties.getY(); + final int width = mDisplaySize.x - x; + final int height = mDisplaySize.y - y; + final boolean visible = mExternalViewProperties.isVisible(); + final Rect clipRect = new Rect(x, y, width + x, height + y); + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.alterWindow(x, y, width, height, visible, + clipRect); + } catch (RemoteException e) { + } + } + }); + return true; + } + + // Placeholder callbacks + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onDetach(); + } catch (RemoteException e) { + } + } + }); + } + + @Override + public void onAttachedToWindow() { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onAttach(null); + } catch (RemoteException e) { + } + } + }); + } + + @Override + public void binderDied() { + if (mCallback != null) { + mCallback.providerDied(); + } + } + + /** + * Sets the component of the {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService} + * to be used for this ExternalView. If a provider is already connected to this view, it is + * first unbound before binding to the new provider. + * @param componentName The {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService} + * to bind to. + */ + public void setProviderComponent(ComponentName componentName) { + // unbind any existing external view provider + if (mExternalViewProvider != null) { + mContext.unbindService(mServiceConnection); + } + if (componentName != null) { + mContext.bindService(new Intent().setComponent(componentName), + mServiceConnection, Context.BIND_AUTO_CREATE); + } + } + + /** + * Called from the host when the keyguard is being shown to the user. + * @param screenOn True if the screen is currently on. + */ + public void onKeyguardShowing(final boolean screenOn) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onKeyguardShowing(screenOn); + } catch (RemoteException e) { + } + } + }); + } + + /** + * Called from the host when the user has unlocked the device. Once this is called the lock + * lock screen should no longer displayed. + */ + public void onKeyguardDismissed() { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onKeyguardDismissed(); + } catch (RemoteException e) { + } + } + }); + } + + /** + * Called from the host when the keyguard is displaying the security screen for the user to + * enter their pin, password, or pattern. + * @param showing True if the bouncer is being show or false when it is dismissed without the + * device being unlocked. + */ + public void onBouncerShowing(final boolean showing) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onBouncerShowing(showing); + } catch (RemoteException e) { + } + } + }); + } + + /** + * Called from the host when the screen is turned on. + */ + public void onScreenTurnedOn() { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onScreenTurnedOn(); + } catch (RemoteException e) { + } + } + }); + } + + /** + * Called from the host when the screen is turned off. + */ + public void onScreenTurnedOff() { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onScreenTurnedOff(); + } catch (RemoteException e) { + } + } + }); + } + + /** + * Called from the host when the user is swiping the lockscreen + * to transition into the live lock screen + * + * @param swipeProgress [0-1] represents the progress of the swipe + */ + public void onLockscreenSlideOffsetChanged(final float swipeProgress) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onLockscreenSlideOffsetChanged(swipeProgress); + } catch (RemoteException e) { + } + } + }); + } + + /** + * External views provided by a + * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService} can be either + * interactive or non-interactive. + * + * <p>A non-interactive component does not receive any input events and functions similar to a + * live wallpaper.</p> + * + * <p>An interactive component can receive input events and allows the user to interact with it + * when the notification panel is not being displayed on top of the external view.</p> + * + * @return True if the current external view is interactive. + */ + public boolean isInteractive() { + return mIsInteractive; + } + + /** + * Registers a {@link cyanogenmod.externalviews.KeyguardExternalView.KeyguardExternalViewCallbacks} + * for receiving events from the + * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService} + * @param callback The callback to register + */ + public void registerKeyguardExternalViewCallback(KeyguardExternalViewCallbacks callback) { + mCallback = callback; + } + + /** + * Unregister a previously registered + * {@link cyanogenmod.externalviews.KeyguardExternalView.KeyguardExternalViewCallbacks} + * @param callback The callback to unregister + */ + public void unregisterKeyguardExternalViewCallback(KeyguardExternalViewCallbacks callback) { + if (mCallback != callback) { + throw new IllegalArgumentException("Callback not registered"); + } + mCallback = null; + } + + /** + * Registers a {@link cyanogenmod.externalviews.KeyguardExternalView.OnWindowAttachmentChangedListener} + * for receiving events from the + * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService} + * @param listener The callback to register + * + * @hide + */ + public void registerOnWindowAttachmentChangedListener( + OnWindowAttachmentChangedListener listener) { + mWindowAttachmentListener = listener; + } + + /** + * Unregister a previously registered + * {@link cyanogenmod.externalviews.KeyguardExternalView.OnWindowAttachmentChangedListener} + * @param listener The callback to unregister + * + * @hide + */ + public void unregisterOnWindowAttachmentChangedListener( + OnWindowAttachmentChangedListener listener) { + if (mWindowAttachmentListener != listener) { + throw new IllegalArgumentException("Callback not registered"); + } + mWindowAttachmentListener = null; + } + + /** + * Callback interface for a {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService} + * to send events to the host's registered + * {@link cyanogenmod.externalviews.KeyguardExternalView.KeyguardExternalViewCallbacks} + */ + public interface KeyguardExternalViewCallbacks { + boolean requestDismiss(); + boolean requestDismissAndStartActivity(Intent intent); + void collapseNotificationPanel(); + void providerDied(); + void slideLockscreenIn(); + } + + /** + * Callback interface for changes to the containing window being attached and detached from the + * window manager. + * @hide + */ + public interface OnWindowAttachmentChangedListener { + void onAttachedToWindow(); + void onDetachedFromWindow(); + } +} diff --git a/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java b/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java new file mode 100644 index 0000000..4c69a0a --- /dev/null +++ b/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java @@ -0,0 +1,636 @@ +/* + * 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.externalviews; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Log; +import android.view.ActionMode; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.SearchEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import com.android.internal.policy.PhoneWindow; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +/** + * A class for providing a view that can be displayed within the lock screen. Applications that + * wish to provide a view to be displayed within the lock screen should extend this service. + * + * <p>Applications extending this class should include the + * {@link cyanogenmod.platform.Manifest.permission#THIRD_PARTY_KEYGUARD} permission in their + * manifest</p> + + * <p>Applications extending this class should also extend + * {@link KeyguardExternalViewProviderService.Provider} and return a new instance of + * {@link KeyguardExternalViewProviderService.Provider} in + * {@link KeyguardExternalViewProviderService#createExternalView(Bundle)}.</p> + */ +public abstract class KeyguardExternalViewProviderService extends Service { + + private static final String TAG = KeyguardExternalViewProviderService.class.getSimpleName(); + private static final boolean DEBUG = false; + + /** + * The action that must be declared as handled by this service. + * + * <p>{@code + * <intent-filter> + * <action android:name="cyanogenmod.externalviews.KeyguardExternalViewProviderService"/> + * </intent-filter> + *}</p> + */ + public static final String SERVICE_INTERFACE = + "cyanogenmod.externalviews.KeyguardExternalViewProviderService"; + + /** + * Name under which an external keyguard view publishes information about itself. + * This meta-data must reference an XML resource containing + * a <code><lockscreen></code> + * tag. + */ + public static final String META_DATA = "cyanogenmod.externalviews.keyguard"; + + private WindowManager mWindowManager; + private final Handler mHandler = new Handler(); + + @Override + public void onCreate() { + super.onCreate(); + + mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + super.onStartCommand(intent, flags, startId); + return START_NOT_STICKY; + } + + @Override + public final IBinder onBind(Intent intent) { + return new IExternalViewProviderFactory.Stub() { + @Override public IBinder createExternalView(final Bundle options) { + FutureTask<IBinder> c = new FutureTask<IBinder>(new Callable<IBinder>() { + @Override + public IBinder call() throws Exception { + return KeyguardExternalViewProviderService.this + .createExternalView(options).mImpl; + } + }); + mHandler.post(c); + try { + return c.get(); + } catch (InterruptedException | ExecutionException e) { + Log.e(TAG, "error: ", e); + return null; + } + } + }; + } + + /** + * Called when the host has bound to this service. + * @param options Optional bundle. This param is currently not used. + * @return The newly created provider. + */ + protected abstract Provider createExternalView(Bundle options); + + /** + * This class provides an interface for the host and service to communicate to each other. + */ + protected abstract class Provider { + private final class ProviderImpl extends IKeyguardExternalViewProvider.Stub + implements Window.Callback { + private final Window mWindow; + private final WindowManager.LayoutParams mParams; + + private boolean mShouldShow = true; + private boolean mAskedShow = false; + + private final RemoteCallbackList<IKeyguardExternalViewCallbacks> mCallbacks = + new RemoteCallbackList<IKeyguardExternalViewCallbacks>(); + + public ProviderImpl(Provider provider) { + mWindow = new PhoneWindow(KeyguardExternalViewProviderService.this); + mWindow.setCallback(this); + ((ViewGroup) mWindow.getDecorView()).addView(onCreateView()); + + mParams = new WindowManager.LayoutParams(); + mParams.type = provider.getWindowType(); + mParams.flags = provider.getWindowFlags(); + mParams.gravity = Gravity.LEFT | Gravity.TOP; + mParams.format = PixelFormat.TRANSPARENT; + } + + @Override + public void onAttach(IBinder windowToken) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mWindowManager.addView(mWindow.getDecorView(), mParams); + Provider.this.onAttach(); + } + }); + } + + @Override + public void onDetach() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mWindowManager.removeView(mWindow.getDecorView()); + Provider.this.onDetach(); + } + }); + } + + @Override + public void onKeyguardShowing(final boolean screenOn) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onKeyguardShowing(screenOn); + } + }); + } + + @Override + public void onKeyguardDismissed() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onKeyguardDismissed(); + } + }); + } + + @Override + public void onBouncerShowing(final boolean showing) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onBouncerShowing(showing); + } + }); + } + + @Override + public void onScreenTurnedOn() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onScreenTurnedOn(); + } + }); + } + + @Override + public void onScreenTurnedOff() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onScreenTurnedOff(); + } + }); + } + + @Override + public void onLockscreenSlideOffsetChanged(final float swipeProgress) + throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onLockscreenSlideOffsetChanged(swipeProgress); + } + }); + } + + @Override + public void alterWindow(final int x, final int y, final int width, final int height, + final boolean visible, final Rect clipRect) { + mHandler.post(new Runnable() { + @Override + public void run() { + mParams.x = x; + mParams.y = y; + mParams.width = width; + mParams.height = height; + + if (DEBUG) Log.d(TAG, mParams.toString()); + + mAskedShow = visible; + + updateVisibility(); + + View decorView = mWindow.getDecorView(); + if (decorView.getVisibility() == View.VISIBLE) { + decorView.setClipBounds(clipRect); + } + + if (mWindow.getDecorView().getVisibility() != View.GONE) + mWindowManager.updateViewLayout(mWindow.getDecorView(), mParams); + } + }); + } + + @Override + public void registerCallback(IKeyguardExternalViewCallbacks callback) { + mCallbacks.register(callback); + } + + @Override + public void unregisterCallback(IKeyguardExternalViewCallbacks callback) { + mCallbacks.unregister(callback); + } + + private void updateVisibility() { + if (DEBUG) Log.d(TAG, "shouldShow = " + mShouldShow + " askedShow = " + mAskedShow); + mWindow.getDecorView().setVisibility(mShouldShow && mAskedShow ? + View.VISIBLE : View.GONE); + } + + // callbacks from provider to host + protected final boolean requestDismiss() { + boolean ret = true; + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + ret &= callback.requestDismiss(); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + return ret; + } + + protected final boolean requestDismissAndStartActivity(final Intent intent) { + boolean ret = true; + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + ret &= callback.requestDismissAndStartActivity(intent); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + return ret; + } + + protected final void collapseNotificationPanel() { + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + callback.collapseNotificationPanel(); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + } + + protected final void setInteractivity(final boolean isInteractive) { + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + callback.setInteractivity(isInteractive); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + } + + public void slideLockscreenIn() { + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + callback.slideLockscreenIn(); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + } + + // region Window callbacks + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return false; + } + + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) { + return false; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + return false; + } + + @Override + public boolean dispatchTrackballEvent(MotionEvent event) { + return false; + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent event) { + return false; + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + return false; + } + + @Override + public View onCreatePanelView(int featureId) { + return null; + } + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + return false; + } + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + return false; + } + + @Override + public boolean onMenuOpened(int featureId, Menu menu) { + return false; + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + return false; + } + + @Override + public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {} + + @Override + public void onContentChanged() {} + + @Override + public void onWindowFocusChanged(boolean hasFocus) {} + + @Override + public void onAttachedToWindow() { + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + callback.onAttachedToWindow(); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + } + + @Override + public void onDetachedFromWindow() { + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + callback.onDetachedFromWindow(); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + } + + @Override + public void onPanelClosed(int featureId, Menu menu) {} + + @Override + public boolean onSearchRequested() { + return false; + } + + @Override + public boolean onSearchRequested(SearchEvent searchEvent) { + return false; + } + + @Override + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { + return null; + } + + @Override + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) { + return null; + } + + @Override + public void onActionModeStarted(ActionMode mode) {} + + @Override + public void onActionModeFinished(ActionMode mode) {} + } + + private final ProviderImpl mImpl = new ProviderImpl(this); + private final Bundle mOptions; + + protected Provider(Bundle options) { + mOptions = options; + } + + protected Bundle getOptions() { + return mOptions; + } + + /** + * Called when the host view is attached to a window. + */ + protected void onAttach() {} + + /** + * Called when the host view is detached from a window. + */ + protected void onDetach() {} + + /** + * Callback used for getting the view to be displayed within the host's content. + * @return The view to be displayed within the host's content. If null is returned no + * content will be displayed. + */ + protected abstract View onCreateView(); + + // keyguard events + + /** + * Called from the host when the keyguard is being shown to the user. + * @param screenOn True if the screen is currently on. + */ + protected abstract void onKeyguardShowing(boolean screenOn); + + /** + * Called from the host when the user has unlocked the device. Once this is called the lock + * lock screen is no longer being displayed. + * + * <p>The view component should enter a paused state when this is called, and save any state + * information that may be needed once the lock screen is displayed again. For example, a + * non-interactive component that provides animated visuals should pause playback of those + * animations and save the state, if necessary, of that animation.</p> + */ + protected abstract void onKeyguardDismissed(); + + /** + * Called from the host when the keyguard is displaying the security screen for the user to + * enter their pin, password, or pattern. + * + * <p>Interactive components will no longer have focus when the bouncer is displayed and + * should enter a paused or idle state while the bouncer is being shown.</p> + * @param showing True if the bouncer is being show or false when it is dismissed without the + * device being unlocked. + */ + protected abstract void onBouncerShowing(boolean showing); + + /** + * Called from the host when the screen is turned on. + * + * <p>The provided view should return to a running state when this is called. For example, + * a non-interactive component that provides animated visuals should resume playback of + * those animations.</p> + */ + protected abstract void onScreenTurnedOn(); + + /** + * Called from the host when the screen is turned off. + * + * <p>The provided view should provided view should pause its activity, if not currently + * in a paused state, and do any work necessary to be ready when the screen is turned + * back on. This will allow for a seamless user experience once the screen is turned on. + * </p> + */ + protected abstract void onScreenTurnedOff(); + + /** + * Called from the host when the user is swiping the lockscreen + * to transition into the live lock screen + * + * @param swipeProgress [0-1] represents the progress of the swipe + */ + protected void onLockscreenSlideOffsetChanged(float swipeProgress) {} + + // callbacks from provider to host + + /** + * Request that the keyguard be dismissed. Calling this method will dismiss the lock + * screen, if it is a not secure, or present the user with the security screen for the user + * to enter their security code to finish dismissing the lock screen. + * + * <p>If the user has a secure lock screen and dismisses the bouncer without entering their + * secure code, the lock screen will not be dismissed and + * {@link KeyguardExternalViewProviderService.Provider#onBouncerShowing(boolean)} will be + * called with {@code onShowing} being set to false, indicating that the lock screen was not + * dismissed as requested.</p> + * @return True if the call succeeded. + */ + protected final boolean requestDismiss() { + return mImpl.requestDismiss(); + } + + /** + * Request that the keyguard be dismissed and the activity provided by the given intent be + * started once the keyguard is dismissed. If a secure lock screen is being used the user + * will need to enter their correct security code to finish dismissing the lock screen. + * + * <p>If the user has a secure lock screen and dismisses the bouncer without entering their + * secure code, the lock screen will not be dismissed and + * {@link KeyguardExternalViewProviderService.Provider#onBouncerShowing(boolean)} will be + * called with onShowing being set to false, indicating that the lock screen was not + * dismissed as requested.</p> + * @param intent An intent specifying an activity to launch. + * @return True if the call succeeded. + */ + protected final boolean requestDismissAndStartActivity(final Intent intent) { + return mImpl.requestDismissAndStartActivity(intent); + } + + /** + * Call this method when you would like to take focus and hide the notification panel. + * + * <p>You should call this method if your component requires focus and the users's + * attention. The user will still be able to bring the notifications back into view by + * sliding down from the status bar. + * Calling this method has no effect for non-interactive components.</p> + */ + protected final void collapseNotificationPanel() { + mImpl.collapseNotificationPanel(); + } + + /** + * This method should be called when the provided view needs to change from interactive to + * non-interactive and vice versa. + * + * <p>Interactive components can receive input focus and receive user interaction while + * non-interactive components never receive focus and are purely visual.</p> + * @param isInteractive + */ + protected final void setInteractivity(final boolean isInteractive) { + mImpl.setInteractivity(isInteractive); + } + + /** + * Call this method when you like to slide in the lockscreen on top of + * your live lockscreen. Only relevant if you use + * {@link KeyguardExternalViewProviderService.Provider#setInteractivity(boolean)} + */ + protected final void slideLockscreenIn() { + mImpl.slideLockscreenIn(); + } + + /*package*/ final int getWindowType() { + return WindowManager.LayoutParams.TYPE_KEYGUARD_PANEL; + } + + /*package*/ final int getWindowFlags() { + return WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | + WindowManager.LayoutParams.FLAG_FULLSCREEN; + } + } +} diff --git a/sdk/src/java/cyanogenmod/hardware/CMHardwareManager.java b/sdk/src/java/cyanogenmod/hardware/CMHardwareManager.java new file mode 100644 index 0000000..5bfb858 --- /dev/null +++ b/sdk/src/java/cyanogenmod/hardware/CMHardwareManager.java @@ -0,0 +1,857 @@ +/* + * Copyright (C) 2015-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.hardware; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import cyanogenmod.app.CMContextConstants; + +import java.io.UnsupportedEncodingException; +import java.lang.IllegalArgumentException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; + +/** + * Manages access to CyanogenMod hardware extensions + * + * <p> + * This manager requires the HARDWARE_ABSTRACTION_ACCESS permission. + * <p> + * To get the instance of this class, utilize CMHardwareManager#getInstance(Context context) + */ +public final class CMHardwareManager { + private static final String TAG = "CMHardwareManager"; + + private static ICMHardwareService sService; + + private Context mContext; + /** + * Adaptive backlight support (this refers to technologies like NVIDIA SmartDimmer, + * QCOM CABL or Samsung CABC) + */ + public static final int FEATURE_ADAPTIVE_BACKLIGHT = 0x1; + + /** + * Color enhancement support + */ + public static final int FEATURE_COLOR_ENHANCEMENT = 0x2; + + /** + * Display RGB color calibration + */ + public static final int FEATURE_DISPLAY_COLOR_CALIBRATION = 0x4; + + /** + * Display gamma calibration + */ + public static final int FEATURE_DISPLAY_GAMMA_CALIBRATION = 0x8; + + /** + * High touch sensitivity for touch panels + */ + public static final int FEATURE_HIGH_TOUCH_SENSITIVITY = 0x10; + + /** + * Hardware navigation key disablement + */ + public static final int FEATURE_KEY_DISABLE = 0x20; + + /** + * Long term orbits (LTO) + */ + public static final int FEATURE_LONG_TERM_ORBITS = 0x40; + + /** + * Serial number other than ro.serialno + */ + public static final int FEATURE_SERIAL_NUMBER = 0x80; + + /** + * Increased display readability in bright light + */ + public static final int FEATURE_SUNLIGHT_ENHANCEMENT = 0x100; + + /** + * Double-tap the touch panel to wake up the device + */ + public static final int FEATURE_TAP_TO_WAKE = 0x200; + + /** + * Variable vibrator intensity + */ + public static final int FEATURE_VIBRATOR = 0x400; + + /** + * Touchscreen hovering + */ + public static final int FEATURE_TOUCH_HOVERING = 0x800; + + /** + * Auto contrast + */ + public static final int FEATURE_AUTO_CONTRAST = 0x1000; + + /** + * Display modes + */ + public static final int FEATURE_DISPLAY_MODES = 0x2000; + + /** + * Persistent storage + */ + public static final int FEATURE_PERSISTENT_STORAGE = 0x4000; + + /** + * Thermal change monitor + */ + public static final int FEATURE_THERMAL_MONITOR = 0x8000; + + /** + * Unique device ID + */ + public static final int FEATURE_UNIQUE_DEVICE_ID = 0x10000; + + private static final List<Integer> BOOLEAN_FEATURES = Arrays.asList( + FEATURE_ADAPTIVE_BACKLIGHT, + FEATURE_COLOR_ENHANCEMENT, + FEATURE_HIGH_TOUCH_SENSITIVITY, + FEATURE_KEY_DISABLE, + FEATURE_SUNLIGHT_ENHANCEMENT, + FEATURE_TAP_TO_WAKE, + FEATURE_TOUCH_HOVERING, + FEATURE_AUTO_CONTRAST, + FEATURE_THERMAL_MONITOR + ); + + private static CMHardwareManager sCMHardwareManagerInstance; + + /** + * @hide to prevent subclassing from outside of the framework + */ + private CMHardwareManager(Context context) { + Context appContext = context.getApplicationContext(); + if (appContext != null) { + mContext = appContext; + } else { + mContext = context; + } + sService = getService(); + + if (context.getPackageManager().hasSystemFeature( + CMContextConstants.Features.HARDWARE_ABSTRACTION) && !checkService()) { + throw new RuntimeException("Unable to get CMHardwareService. 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.hardware.CMHardwareManager} + * @param context + * @return {@link CMHardwareManager} + */ + public static CMHardwareManager getInstance(Context context) { + if (sCMHardwareManagerInstance == null) { + sCMHardwareManagerInstance = new CMHardwareManager(context); + } + return sCMHardwareManagerInstance; + } + + /** @hide */ + public static ICMHardwareService getService() { + if (sService != null) { + return sService; + } + IBinder b = ServiceManager.getService(CMContextConstants.CM_HARDWARE_SERVICE); + if (b != null) { + sService = ICMHardwareService.Stub.asInterface(b); + return sService; + } + return null; + } + + /** + * @return the supported features bitmask + */ + public int getSupportedFeatures() { + try { + if (checkService()) { + return sService.getSupportedFeatures(); + } + } catch (RemoteException e) { + } + return 0; + } + + /** + * Determine if a CM Hardware feature is supported on this device + * + * @param feature The CM Hardware feature to query + * + * @return true if the feature is supported, false otherwise. + */ + public boolean isSupported(int feature) { + return feature == (getSupportedFeatures() & feature); + } + + /** + * Determine if the given feature is enabled or disabled. + * + * Only used for features which have simple enable/disable controls. + * + * @param feature the CM Hardware feature to query + * + * @return true if the feature is enabled, false otherwise. + */ + public boolean get(int feature) { + if (!BOOLEAN_FEATURES.contains(feature)) { + throw new IllegalArgumentException(feature + " is not a boolean"); + } + + try { + if (checkService()) { + return sService.get(feature); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * Enable or disable the given feature + * + * Only used for features which have simple enable/disable controls. + * + * @param feature the CM Hardware feature to set + * @param enable true to enable, false to disale + * + * @return true if the feature is enabled, false otherwise. + */ + public boolean set(int feature, boolean enable) { + if (!BOOLEAN_FEATURES.contains(feature)) { + throw new IllegalArgumentException(feature + " is not a boolean"); + } + + try { + if (checkService()) { + return sService.set(feature, enable); + } + } catch (RemoteException e) { + } + return false; + } + + private int getArrayValue(int[] arr, int idx, int defaultValue) { + if (arr == null || arr.length <= idx) { + return defaultValue; + } + + return arr[idx]; + } + + /** + * {@hide} + */ + public static final int VIBRATOR_INTENSITY_INDEX = 0; + /** + * {@hide} + */ + public static final int VIBRATOR_DEFAULT_INDEX = 1; + /** + * {@hide} + */ + public static final int VIBRATOR_MIN_INDEX = 2; + /** + * {@hide} + */ + public static final int VIBRATOR_MAX_INDEX = 3; + /** + * {@hide} + */ + public static final int VIBRATOR_WARNING_INDEX = 4; + + private int[] getVibratorIntensityArray() { + try { + if (checkService()) { + return sService.getVibratorIntensity(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return The current vibrator intensity. + */ + public int getVibratorIntensity() { + return getArrayValue(getVibratorIntensityArray(), VIBRATOR_INTENSITY_INDEX, 0); + } + + /** + * @return The default vibrator intensity. + */ + public int getVibratorDefaultIntensity() { + return getArrayValue(getVibratorIntensityArray(), VIBRATOR_DEFAULT_INDEX, 0); + } + + /** + * @return The minimum vibrator intensity. + */ + public int getVibratorMinIntensity() { + return getArrayValue(getVibratorIntensityArray(), VIBRATOR_MIN_INDEX, 0); + } + + /** + * @return The maximum vibrator intensity. + */ + public int getVibratorMaxIntensity() { + return getArrayValue(getVibratorIntensityArray(), VIBRATOR_MAX_INDEX, 0); + } + + /** + * @return The warning threshold vibrator intensity. + */ + public int getVibratorWarningIntensity() { + return getArrayValue(getVibratorIntensityArray(), VIBRATOR_WARNING_INDEX, 0); + } + + /** + * Set the current vibrator intensity + * + * @param intensity the intensity to set, between {@link #getVibratorMinIntensity()} and + * {@link #getVibratorMaxIntensity()} inclusive. + * + * @return true on success, false otherwise. + */ + public boolean setVibratorIntensity(int intensity) { + try { + if (checkService()) { + return sService.setVibratorIntensity(intensity); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_RED_INDEX = 0; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_GREEN_INDEX = 1; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_BLUE_INDEX = 2; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_DEFAULT_INDEX = 3; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_MIN_INDEX = 4; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_MAX_INDEX = 5; + + private int[] getDisplayColorCalibrationArray() { + try { + if (checkService()) { + return sService.getDisplayColorCalibration(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return the current RGB calibration, where int[0] = R, int[1] = G, int[2] = B. + */ + public int[] getDisplayColorCalibration() { + int[] arr = getDisplayColorCalibrationArray(); + if (arr == null || arr.length < 3) { + return null; + } + return Arrays.copyOf(arr, 3); + } + + /** + * @return the default value for all colors + */ + public int getDisplayColorCalibrationDefault() { + return getArrayValue(getDisplayColorCalibrationArray(), COLOR_CALIBRATION_DEFAULT_INDEX, 0); + } + + /** + * @return The minimum value for all colors + */ + public int getDisplayColorCalibrationMin() { + return getArrayValue(getDisplayColorCalibrationArray(), COLOR_CALIBRATION_MIN_INDEX, 0); + } + + /** + * @return The minimum value for all colors + */ + public int getDisplayColorCalibrationMax() { + return getArrayValue(getDisplayColorCalibrationArray(), COLOR_CALIBRATION_MAX_INDEX, 0); + } + + /** + * Set the display color calibration to the given rgb triplet + * + * @param rgb RGB color calibration. Each value must be between + * {@link getDisplayColorCalibrationMin()} and {@link getDisplayColorCalibrationMax()}, + * inclusive. + * + * @return true on success, false otherwise. + */ + public boolean setDisplayColorCalibration(int[] rgb) { + try { + if (checkService()) { + return sService.setDisplayColorCalibration(rgb); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * Write a string to persistent storage, which persists thru factory reset + * + * @param key String identifier for this item. Must not exceed 64 characters. + * @param value The UTF-8 encoded string to store of at least 1 character. null deletes the key/value pair. + * @return true on success + */ + public boolean writePersistentString(String key, String value) { + try { + if (checkService()) { + return sService.writePersistentBytes(key, + value == null ? null : value.getBytes("UTF-8")); + } + } catch (RemoteException e) { + } catch (UnsupportedEncodingException e) { + Log.e(TAG, e.getMessage(), e); + } + return false; + } + + /** + * Write an integer to persistent storage, which persists thru factory reset + * + * @param key String identifier for this item. Must not exceed 64 characters. + * @param value The integer to store + * @return true on success + */ + public boolean writePersistentInt(String key, int value) { + try { + if (checkService()) { + return sService.writePersistentBytes(key, + ByteBuffer.allocate(4).putInt(value).array()); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * Write a byte array to persistent storage, which persists thru factory reset + * + * @param key String identifier for this item. Must not exceed 64 characters. + * @param value The byte array to store, must be 1-4096 bytes. null deletes the key/value pair. + * @return true on success + */ + public boolean writePersistentBytes(String key, byte[] value) { + try { + if (checkService()) { + return sService.writePersistentBytes(key, value); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * Read a string from persistent storage + * + * @param key String identifier for this item. Must not exceed 64 characters. + * @return the stored UTF-8 encoded string, null if not found + */ + public String readPersistentString(String key) { + try { + if (checkService()) { + byte[] bytes = sService.readPersistentBytes(key); + if (bytes != null) { + return new String(bytes, "UTF-8"); + } + } + } catch (RemoteException e) { + } catch (UnsupportedEncodingException e) { + Log.e(TAG, e.getMessage(), e); + } + return null; + } + + /** + * Read an integer from persistent storage + * + * @param key String identifier for this item. Must not exceed 64 characters. + * @return the stored integer, zero if not found + */ + public int readPersistentInt(String key) { + try { + if (checkService()) { + byte[] bytes = sService.readPersistentBytes(key); + if (bytes != null) { + return ByteBuffer.wrap(bytes).getInt(); + } + } + } catch (RemoteException e) { + } + return 0; + } + + /** + * Read a byte array from persistent storage + * + * @param key String identifier for this item. Must not exceed 64 characters. + * @return the stored byte array, null if not found + */ + public byte[] readPersistentBytes(String key) { + try { + if (checkService()) { + return sService.readPersistentBytes(key); + } + } catch (RemoteException e) { + } + return null; + } + + /** Delete an object from persistent storage + * + * @param key String identifier for this item + * @return true if an item was deleted + */ + public boolean deletePersistentObject(String key) { + try { + if (checkService()) { + return sService.writePersistentBytes(key, null); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * {@hide} + */ + public static final int GAMMA_CALIBRATION_RED_INDEX = 0; + /** + * {@hide} + */ + public static final int GAMMA_CALIBRATION_GREEN_INDEX = 1; + /** + * {@hide} + */ + public static final int GAMMA_CALIBRATION_BLUE_INDEX = 2; + /** + * {@hide} + */ + public static final int GAMMA_CALIBRATION_MIN_INDEX = 3; + /** + * {@hide} + */ + public static final int GAMMA_CALIBRATION_MAX_INDEX = 4; + + private int[] getDisplayGammaCalibrationArray(int idx) { + try { + if (checkService()) { + return sService.getDisplayGammaCalibration(idx); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return the number of RGB controls the device supports + */ + @Deprecated + public int getNumGammaControls() { + try { + if (checkService()) { + return sService.getNumGammaControls(); + } + } catch (RemoteException e) { + } + return 0; + } + + /** + * @param the control to query + * + * @return the current RGB gamma calibration for the given control + */ + @Deprecated + public int[] getDisplayGammaCalibration(int idx) { + int[] arr = getDisplayGammaCalibrationArray(idx); + if (arr == null || arr.length < 3) { + return null; + } + return Arrays.copyOf(arr, 3); + } + + /** + * @return the minimum value for all colors + */ + @Deprecated + public int getDisplayGammaCalibrationMin() { + return getArrayValue(getDisplayGammaCalibrationArray(0), GAMMA_CALIBRATION_MIN_INDEX, 0); + } + + /** + * @return the maximum value for all colors + */ + @Deprecated + public int getDisplayGammaCalibrationMax() { + return getArrayValue(getDisplayGammaCalibrationArray(0), GAMMA_CALIBRATION_MAX_INDEX, 0); + } + + /** + * Set the display gamma calibration for a specific control + * + * @param idx the control to set + * @param rgb RGB color calibration. Each value must be between + * {@link getDisplayGammaCalibrationMin()} and {@link getDisplayGammaCalibrationMax()}, + * inclusive. + * + * @return true on success, false otherwise. + */ + @Deprecated + public boolean setDisplayGammaCalibration(int idx, int[] rgb) { + try { + if (checkService()) { + return sService.setDisplayGammaCalibration(idx, rgb); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * @return the source location of LTO data, or null on failure + */ + public String getLtoSource() { + try { + if (checkService()) { + return sService.getLtoSource(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return the destination location of LTO data, or null on failure + */ + public String getLtoDestination() { + try { + if (checkService()) { + return sService.getLtoDestination(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return the interval, in milliseconds, to trigger LTO data download + */ + public long getLtoDownloadInterval() { + try { + if (checkService()) { + return sService.getLtoDownloadInterval(); + } + } catch (RemoteException e) { + } + return 0; + } + + /** + * @return the serial number to display instead of ro.serialno, or null on failure + */ + public String getSerialNumber() { + try { + if (checkService()) { + return sService.getSerialNumber(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return an id that's both unique and deterministic for the device + */ + public String getUniqueDeviceId() { + try { + if (checkService()) { + return sService.getUniqueDeviceId(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return true if adaptive backlight should be enabled when sunlight enhancement + * is enabled. + */ + public boolean requireAdaptiveBacklightForSunlightEnhancement() { + try { + if (checkService()) { + return sService.requireAdaptiveBacklightForSunlightEnhancement(); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * @return true if this implementation does it's own lux metering + */ + public boolean isSunlightEnhancementSelfManaged() { + try { + if (checkService()) { + return sService.isSunlightEnhancementSelfManaged(); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * @return a list of available display modes on the devices + */ + public DisplayMode[] getDisplayModes() { + try { + if (checkService()) { + return sService.getDisplayModes(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return the currently active display mode + */ + public DisplayMode getCurrentDisplayMode() { + try { + if (checkService()) { + return sService.getCurrentDisplayMode(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return the default display mode to be set on boot + */ + public DisplayMode getDefaultDisplayMode() { + try { + if (checkService()) { + return sService.getDefaultDisplayMode(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return true if setting the mode was successful + */ + public boolean setDisplayMode(DisplayMode mode, boolean makeDefault) { + try { + if (checkService()) { + return sService.setDisplayMode(mode, makeDefault); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * @return true if service is valid + */ + private boolean checkService() { + if (sService == null) { + Log.w(TAG, "not connected to CMHardwareManagerService"); + return false; + } + return true; + } + + /** + * @return current thermal {@link cyanogenmod.hardware.ThermalListenerCallback.State} + */ + public int getThermalState() { + try { + if (checkService()) { + return sService.getThermalState(); + } + } catch (RemoteException e) { + } + return ThermalListenerCallback.State.STATE_UNKNOWN; + } + + /** + * Register a callback to be notified of thermal state changes + * @return boolean indicating whether register succeeded or failed + */ + public boolean registerThermalListener(ThermalListenerCallback thermalCallback) { + try { + if (checkService()) { + return sService.registerThermalListener(thermalCallback); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * Unregister a callback previously registered to be notified of thermal state changes + * @return boolean indicating whether un-registering succeeded or failed + */ + public boolean unRegisterThermalListener(ThermalListenerCallback thermalCallback) { + try { + if (checkService()) { + return sService.unRegisterThermalListener(thermalCallback); + } + } catch (RemoteException e) { + } + return false; + } +} diff --git a/sdk/src/java/cyanogenmod/hardware/DisplayMode.aidl b/sdk/src/java/cyanogenmod/hardware/DisplayMode.aidl new file mode 100644 index 0000000..a4f9163 --- /dev/null +++ b/sdk/src/java/cyanogenmod/hardware/DisplayMode.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.hardware; + +parcelable DisplayMode; diff --git a/sdk/src/java/cyanogenmod/hardware/DisplayMode.java b/sdk/src/java/cyanogenmod/hardware/DisplayMode.java new file mode 100644 index 0000000..77172b8 --- /dev/null +++ b/sdk/src/java/cyanogenmod/hardware/DisplayMode.java @@ -0,0 +1,109 @@ +/* + * 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.hardware; + +import android.os.Parcel; +import android.os.Parcelable; + +import cyanogenmod.os.Build; +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; + +/** + * Display Modes API + * + * A device may implement a list of preset display modes for different + * viewing intents, such as movies, photos, or extra vibrance. These + * modes may have multiple components such as gamma correction, white + * point adjustment, etc, but are activated by a single control point. + * + * This API provides support for enumerating and selecting the + * modes supported by the hardware. + * + * A DisplayMode is referenced by it's identifier and carries an + * associated name (up to the user to translate this value). + */ +public class DisplayMode implements Parcelable { + public final int id; + public final String name; + + public DisplayMode(int id, String name) { + this.id = id; + this.name = name; + } + + private DisplayMode(Parcel parcel) { + // Read parcelable version via the Concierge + ParcelInfo parcelInfo = Concierge.receiveParcel(parcel); + int parcelableVersion = parcelInfo.getParcelVersion(); + + // temp vars + int tmpId = -1; + String tmpName = null; + + if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) { + tmpId = parcel.readInt(); + if (parcel.readInt() != 0) { + tmpName = parcel.readString(); + } + } + + // set temps + this.id = tmpId; + this.name = tmpName; + + // 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); + + // ==== BOYSENBERRY ===== + out.writeInt(id); + if (name != null) { + out.writeInt(1); + out.writeString(name); + } else { + out.writeInt(0); + } + + // Complete the parcel info for the concierge + parcelInfo.complete(); + } + + /** @hide */ + public static final Parcelable.Creator<DisplayMode> CREATOR = + new Parcelable.Creator<DisplayMode>() { + public DisplayMode createFromParcel(Parcel in) { + return new DisplayMode(in); + } + + @Override + public DisplayMode[] newArray(int size) { + return new DisplayMode[size]; + } + }; + +} diff --git a/sdk/src/java/cyanogenmod/hardware/ICMHardwareService.aidl b/sdk/src/java/cyanogenmod/hardware/ICMHardwareService.aidl new file mode 100644 index 0000000..a1ae65b --- /dev/null +++ b/sdk/src/java/cyanogenmod/hardware/ICMHardwareService.aidl @@ -0,0 +1,61 @@ +/** + * 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.hardware; + +import cyanogenmod.hardware.DisplayMode; +import cyanogenmod.hardware.IThermalListenerCallback; + +/** @hide */ +interface ICMHardwareService { + + int getSupportedFeatures(); + boolean get(int feature); + boolean set(int feature, boolean enable); + + int[] getDisplayColorCalibration(); + boolean setDisplayColorCalibration(in int[] rgb); + + int getNumGammaControls(); + int[] getDisplayGammaCalibration(int idx); + boolean setDisplayGammaCalibration(int idx, in int[] rgb); + + int[] getVibratorIntensity(); + boolean setVibratorIntensity(int intensity); + + String getLtoSource(); + String getLtoDestination(); + long getLtoDownloadInterval(); + + String getSerialNumber(); + + boolean requireAdaptiveBacklightForSunlightEnhancement(); + + DisplayMode[] getDisplayModes(); + DisplayMode getCurrentDisplayMode(); + DisplayMode getDefaultDisplayMode(); + boolean setDisplayMode(in DisplayMode mode, boolean makeDefault); + + boolean writePersistentBytes(String key, in byte[] bytes); + byte[] readPersistentBytes(String key); + + int getThermalState(); + boolean registerThermalListener(IThermalListenerCallback callback); + boolean unRegisterThermalListener(IThermalListenerCallback callback); + boolean isSunlightEnhancementSelfManaged(); + + String getUniqueDeviceId(); +} diff --git a/sdk/src/java/cyanogenmod/hardware/IThermalListenerCallback.aidl b/sdk/src/java/cyanogenmod/hardware/IThermalListenerCallback.aidl new file mode 100644 index 0000000..1a53af2 --- /dev/null +++ b/sdk/src/java/cyanogenmod/hardware/IThermalListenerCallback.aidl @@ -0,0 +1,21 @@ +/** + * 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.hardware; + +interface IThermalListenerCallback { + void onThermalChanged(int state); +} diff --git a/sdk/src/java/cyanogenmod/hardware/ThermalListenerCallback.java b/sdk/src/java/cyanogenmod/hardware/ThermalListenerCallback.java new file mode 100644 index 0000000..3ef312d --- /dev/null +++ b/sdk/src/java/cyanogenmod/hardware/ThermalListenerCallback.java @@ -0,0 +1,44 @@ +/** + * 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.hardware; + +/** + * Callback class to register for thermal state changes + */ +public abstract class ThermalListenerCallback extends IThermalListenerCallback.Stub { + public static final class State { + public static final int STATE_UNKNOWN = -1; + public static final int STATE_COOL = 0; + public static final int STATE_WARM_FALLING = 1; + public static final int STATE_WARM_RISING = 2; + public static final int STATE_CRITICAL = 3; + public static final String toString(int state) { + switch (state) { + case STATE_COOL: + return "STATE_COOL"; + case STATE_WARM_FALLING: + return "STATE_WARM_FALLING"; + case STATE_WARM_RISING: + return "STATE_WARM_RISING"; + case STATE_CRITICAL: + return "STATE_CRITICAL"; + default: + return "STATE_UNKNOWN"; + } + } + } +} diff --git a/sdk/src/java/cyanogenmod/media/MediaRecorder.java b/sdk/src/java/cyanogenmod/media/MediaRecorder.java new file mode 100644 index 0000000..c28cc93 --- /dev/null +++ b/sdk/src/java/cyanogenmod/media/MediaRecorder.java @@ -0,0 +1,84 @@ +/* + * 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.media; + +public class MediaRecorder { + /** + * Allows an application to listen passively to the device microphone in the background + * to detect speech of a hotword or phrase. + * + * This is a system|signature permission. + */ + public static final String CAPTURE_AUDIO_HOTWORD_PERMISSION + = "android.permission.CAPTURE_AUDIO_HOTWORD"; + + /** + * <p>Broadcast Action: The state of the HOTWORD audio input has changed.:</p> + * <ul> + * <li><em>state</em> - A String value indicating the state of the input. + * {@link #EXTRA_HOTWORD_INPUT_STATE}. The value will be one of: + * {@link android.media.AudioRecord#RECORDSTATE_RECORDING} or + * {@link android.media.AudioRecord#RECORDSTATE_STOPPED}. + * </li> + * <li><em>package</em> - A String value indicating the package name of the application + * that currently holds the HOTWORD input. + * {@link #EXTRA_CURRENT_PACKAGE_NAME} + * </li> + + * </ul> + * + * <p class="note">This is a protected intent that can only be sent + * by the system. It can only be received by packages that hold + * {@link android.Manifest.permission#CAPTURE_AUDIO_HOTWORD}. + */ + //@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_HOTWORD_INPUT_CHANGED + = "com.cyanogenmod.intent.action.HOTWORD_INPUT_CHANGED"; + + /** + * Extra for {@link #ACTION_HOTWORD_INPUT_CHANGED} that provides the package name of the + * app in that controlled the HOTWORD input when the state changed. Can be reused for other + * purposes. + */ + public static final String EXTRA_CURRENT_PACKAGE_NAME = + "com.cyanogenmod.intent.extra.CURRENT_PACKAGE_NAME"; + + /** + * Extra for {@link #ACTION_HOTWORD_INPUT_CHANGED} that provides the state of + * the input when the broadcast action was sent. + * @hide + */ + public static final String EXTRA_HOTWORD_INPUT_STATE = + "com.cyanogenmod.intent.extra.HOTWORD_INPUT_STATE"; + + + public static class AudioSource { + /** + * Audio source for preemptible, low-priority software hotword detection + * It presents the same gain and pre processing tuning as + * {@link android.media.MediaRecorder.AudioSource#VOICE_RECOGNITION}. + * <p> + * An application should use this audio source when it wishes to do + * always-on software hotword detection, while gracefully giving in to any other application + * that might want to read from the microphone. + * </p> + * You must hold {@link cyanogenmod.media.MediaRecorder#CAPTURE_AUDIO_HOTWORD_PERMISSION} + * to use this audio source. + */ + public static final int HOTWORD = 1999; + } +} diff --git a/sdk/src/java/cyanogenmod/os/Build.java b/sdk/src/java/cyanogenmod/os/Build.java new file mode 100644 index 0000000..ce7b8fb --- /dev/null +++ b/sdk/src/java/cyanogenmod/os/Build.java @@ -0,0 +1,148 @@ +/* + * 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.os; + +import android.os.SystemProperties; +import android.text.TextUtils; +import android.util.SparseArray; + +/** + * Information about the current CyanogenMod build, extracted from system properties. + */ +public class Build { + /** Value used for when a build property is unknown. */ + public static final String UNKNOWN = "unknown"; + + /** A build ID utilized to distinguish cyanogenmod versions */ + public static final String CYANOGENMOD_VERSION = "ro.cm.version"; + + /** A build ID string meant for displaying to the user */ + public static final String CYANOGENMOD_DISPLAY_VERSION = "ro.cm.display.version"; + + private static final SparseArray<String> sdkMap; + static + { + sdkMap = new SparseArray<String>(); + sdkMap.put(CM_VERSION_CODES.APRICOT, "Apricot"); + sdkMap.put(CM_VERSION_CODES.BOYSENBERRY, "Boysenberry"); + sdkMap.put(CM_VERSION_CODES.CANTALOUPE, "Cantaloupe"); + sdkMap.put(CM_VERSION_CODES.DRAGON_FRUIT, "Dragon Fruit"); + sdkMap.put(CM_VERSION_CODES.ELDERBERRY, "Elderberry"); + } + + /** Various version strings. */ + public static class CM_VERSION { + /** + * The user-visible SDK version of the framework; its possible + * values are defined in {@link Build.CM_VERSION_CODES}. + * + * Will return 0 if the device does not support the CM SDK. + */ + public static final int SDK_INT = SystemProperties.getInt( + "ro.cm.build.version.plat.sdk", 0); + } + + /** + * Enumeration of the currently known SDK version codes. These are the + * values that can be found in {@link CM_VERSION#SDK_INT}. Version numbers + * increment monotonically with each official platform release. + * + * To programmatically validate that a given API is available for use on the device, + * you can quickly check if the SDK_INT from the OS is provided and is greater or equal + * to the API level that your application is targeting. + * + * <p>Example for validating that Profiles API is available + * <pre class="prettyprint"> + * private void removeActiveProfile() { + * Make sure we're running on BoysenBerry or higher to use Profiles API + * if (Build.CM_VERSION.SDK_INT >= Build.CM_VERSION_CODES.BOYSENBERRY) { + * ProfileManager profileManager = ProfileManager.getInstance(this); + * Profile activeProfile = profileManager.getActiveProfile(); + * if (activeProfile != null) { + * profileManager.removeProfile(activeProfile); + * } + * } + * } + * </pre> + */ + public static class CM_VERSION_CODES { + /** + * June 2015: The first version of the platform sdk for CyanogenMod + */ + public static final int APRICOT = 1; + + /** + * September 2015: The second version of the platform sdk for CyanogenMod + * + * <p>Applications targeting this or a later release will get these + * new features:</p> + * <ul> + * <li>Profiles API via {@link cyanogenmod.app.ProfileManager} + * <li>New Expanded Styles for Custom Tiles via + * {@link cyanogenmod.app.CustomTile.RemoteExpandedStyle} + * <li>Hardware Abstraction Framework Access via + * {@link cyanogenmod.hardware.CMHardwareManager} (Not for use by 3rd parties) + * <li>MSIM API via {@link cyanogenmod.app.CMTelephonyManager} + * <li>Interface for partners via {@link cyanogenmod.app.PartnerInterface} + * <li>Introductory Settings Provider {@link cyanogenmod.providers.CMSettings} + * <li>AlarmClock API via {@link cyanogenmod.alarmclock.CyanogenModAlarmClock} + * </ul> + */ + public static final int BOYSENBERRY = 2; + + /** + * November - December 2015: The third iteration of the platform sdk for CyanogenMod + * Transition api level that is mostly 1:1 to {@link #BOYSENBERRY} + */ + public static final int CANTALOUPE = 3; + + /** + * January 2016: The 4th iteration of the platform sdk for CyanogenMod + * + * <p>Applications targeting this or a later version will get access to these + * new features:</p> + * <ul> + * <li>External views api, and specifically Keyguard interfaces for making + * live lockscreens via {@link cyanogenmod.externalviews.KeyguardExternalView}</li> + * <li>Inclusion of the PerformanceManager interfaces, allowing an application to specify + * the type of mode to have the device be placed in via + * {@link cyanogenmod.power.PerformanceManager}</li> + * <li>Numerous new "System" settings exposed via the + * {@link cyanogenmod.providers.CMSettings.System} interface</li> + * </ul> + */ + public static final int DRAGON_FRUIT = 4; + + /** + * Future M Release - 2nd quarter 2016 + */ + public static final int ELDERBERRY = 5; + } + + /** + * Retrieve the name for the SDK int + * @param sdkInt + * @return name of the SDK int, {@link #UNKNOWN) if not known + */ + public static String getNameForSDKInt(int sdkInt) { + final String name = sdkMap.get(sdkInt); + if (TextUtils.isEmpty(name)) { + return UNKNOWN; + } + return name; + } +} diff --git a/sdk/src/java/cyanogenmod/os/Concierge.java b/sdk/src/java/cyanogenmod/os/Concierge.java new file mode 100644 index 0000000..2463dec --- /dev/null +++ b/sdk/src/java/cyanogenmod/os/Concierge.java @@ -0,0 +1,153 @@ +/* + * 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.os; + +import android.os.Parcel; + +import cyanogenmod.os.Build.CM_VERSION_CODES; + +/** + * Simply, Concierge handles your parcels and makes sure they get marshalled and unmarshalled + * correctly when cross IPC boundaries even when there is a version mismatch between the client + * sdk level and the framework implementation. + * + * <p>On incoming parcel (to be unmarshalled): + * + * <pre class="prettyprint"> + * ParcelInfo incomingParcelInfo = Concierge.receiveParcel(incomingParcel); + * int parcelableVersion = incomingParcelInfo.getParcelVersion(); + * + * // Do unmarshalling steps here iterating over every plausible version + * + * // Complete the process + * incomingParcelInfo.complete(); + * </pre> + * + * <p>On outgoing parcel (to be marshalled): + * + * <pre class="prettyprint"> + * ParcelInfo outgoingParcelInfo = Concierge.prepareParcel(incomingParcel); + * + * // Do marshalling steps here iterating over every plausible version + * + * // Complete the process + * outgoingParcelInfo.complete(); + * </pre> + */ +public final class Concierge { + + /** Not instantiable */ + private Concierge() { + // Don't instantiate + } + + /** + * Since there might be a case where new versions of the cm framework use applications running + * old versions of the protocol (and thus old versions of this class), we need a versioning + * system for the parcels sent between the core framework and its sdk users. + * + * This parcelable version should be the latest version API version listed in + * {@link CM_VERSION_CODES} + * @hide + */ + public static final int PARCELABLE_VERSION = CM_VERSION_CODES.ELDERBERRY; + + /** + * Tell the concierge to receive our parcel, so we can get information from it. + * + * MUST CALL {@link ParcelInfo#complete()} AFTER UNMARSHALLING. + * + * @param parcel Incoming parcel to be unmarshalled + * @return {@link ParcelInfo} containing parcel information, specifically the version. + */ + public static ParcelInfo receiveParcel(Parcel parcel) { + return new ParcelInfo(parcel); + } + + /** + * Prepare a parcel for the Concierge. + * + * MUST CALL {@link ParcelInfo#complete()} AFTER MARSHALLING. + * + * @param parcel Outgoing parcel to be marshalled + * @return {@link ParcelInfo} containing parcel information, specifically the version. + */ + public static ParcelInfo prepareParcel(Parcel parcel) { + return new ParcelInfo(parcel, PARCELABLE_VERSION); + } + + /** + * Parcel header info specific to the Parcel object that is passed in via + * {@link #prepareParcel(Parcel)} or {@link #receiveParcel(Parcel)}. The exposed method + * of {@link #getParcelVersion()} gets the api level of the parcel object. + */ + public final static class ParcelInfo { + private Parcel mParcel; + private int mParcelableVersion; + private int mParcelableSize; + private int mStartPosition; + private int mSizePosition; + private boolean mCreation = false; + + ParcelInfo(Parcel parcel) { + mCreation = false; + mParcel = parcel; + mParcelableVersion = parcel.readInt(); + mParcelableSize = parcel.readInt(); + mStartPosition = parcel.dataPosition(); + } + + ParcelInfo(Parcel parcel, int parcelableVersion) { + mCreation = true; + mParcel = parcel; + mParcelableVersion = parcelableVersion; + + // Write parcelable version, make sure to define explicit changes + // within {@link #PARCELABLE_VERSION); + mParcel.writeInt(mParcelableVersion); + + // Inject a placeholder that will store the parcel size from this point on + // (not including the size itself). + mSizePosition = parcel.dataPosition(); + mParcel.writeInt(0); + mStartPosition = parcel.dataPosition(); + } + + /** + * Get the parcel version from the {@link Parcel} received by the Concierge. + * @return {@link #PARCELABLE_VERSION} of the {@link Parcel} + */ + public int getParcelVersion() { + return mParcelableVersion; + } + + /** + * Complete the {@link ParcelInfo} for the Concierge. + */ + public void complete() { + if (mCreation) { + // Go back and write size + mParcelableSize = mParcel.dataPosition() - mStartPosition; + mParcel.setDataPosition(mSizePosition); + mParcel.writeInt(mParcelableSize); + mParcel.setDataPosition(mStartPosition + mParcelableSize); + } else { + mParcel.setDataPosition(mStartPosition + mParcelableSize); + } + } + } +} diff --git a/sdk/src/java/cyanogenmod/power/IPerformanceManager.aidl b/sdk/src/java/cyanogenmod/power/IPerformanceManager.aidl new file mode 100644 index 0000000..bf44ac9 --- /dev/null +++ b/sdk/src/java/cyanogenmod/power/IPerformanceManager.aidl @@ -0,0 +1,31 @@ +/* + * Copyright 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.power; + +/** @hide */ +interface IPerformanceManager { + + oneway void cpuBoost(int duration); + + boolean setPowerProfile(int profile); + + int getPowerProfile(); + + int getNumberOfProfiles(); + + boolean getProfileHasAppProfiles(int profile); +} diff --git a/sdk/src/java/cyanogenmod/power/PerformanceManager.java b/sdk/src/java/cyanogenmod/power/PerformanceManager.java new file mode 100644 index 0000000..9b44399 --- /dev/null +++ b/sdk/src/java/cyanogenmod/power/PerformanceManager.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.power; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import cyanogenmod.app.CMContextConstants; + +/** + * + */ +public class PerformanceManager { + + public static final String TAG = "PerformanceManager"; + + /** + * Power save profile + * + * This mode sacrifices performance for maximum power saving. + */ + public static final int PROFILE_POWER_SAVE = 0; + + /** + * Balanced power profile + * + * The default mode for balanced power savings and performance + */ + public static final int PROFILE_BALANCED = 1; + + /** + * High-performance profile + * + * This mode sacrifices power for maximum performance + */ + public static final int PROFILE_HIGH_PERFORMANCE = 2; + + /** + * Power save bias profile + * + * This mode decreases performance slightly to improve + * power savings. + */ + public static final int PROFILE_BIAS_POWER_SAVE = 3; + + /** + * Performance bias profile + * + * This mode improves performance at the cost of some power. + */ + public static final int PROFILE_BIAS_PERFORMANCE = 4; + + private int mNumberOfProfiles = 0; + + /** + * Broadcast sent when profile is changed + */ + public static final String POWER_PROFILE_CHANGED = "cyanogenmod.power.PROFILE_CHANGED"; + + private static IPerformanceManager sService; + private static PerformanceManager sInstance; + + private PerformanceManager(Context context) { + sService = getService(); + if (context.getPackageManager().hasSystemFeature( + CMContextConstants.Features.PERFORMANCE) && sService == null) { + throw new RuntimeException("Unable to get PerformanceManagerService. The service" + + " either crashed, was not started, or the interface has been called to early" + + " in SystemServer init"); + } + try { + if (sService != null) { + mNumberOfProfiles = sService.getNumberOfProfiles(); + } + } catch (RemoteException e) { + } + } + + public static PerformanceManager getInstance(Context context) { + if (sInstance == null) { + sInstance = new PerformanceManager(context); + } + return sInstance; + } + + /** @hide */ + public static IPerformanceManager getService() { + if (sService != null) { + return sService; + } + IBinder b = ServiceManager.getService(CMContextConstants.CM_PERFORMANCE_SERVICE); + if (b != null) { + sService = IPerformanceManager.Stub.asInterface(b); + return sService; + } + return null; + } + + private boolean checkService() { + if (sService == null) { + Log.w(TAG, "not connected to PerformanceManagerService"); + return false; + } + return true; + } + + /** + * Boost the CPU. Boosts the cpu for the given duration in microseconds. + * Requires the {@link android.Manifest.permission#CPU_BOOST} permission. + * + * @param duration in microseconds to boost the CPU + * @hide + */ + public void cpuBoost(int duration) + { + try { + if (checkService()) { + sService.cpuBoost(duration); + } + } catch (RemoteException e) { + } + } + + /** + * Returns the number of supported profiles, -1 if unsupported + * This is queried via the PowerHAL. + */ + public int getNumberOfProfiles() { + return mNumberOfProfiles; + } + + /** + * Set the system power profile + * + * @throws IllegalArgumentException if invalid + */ + public boolean setPowerProfile(int profile) { + if (mNumberOfProfiles < 1) { + throw new IllegalArgumentException("Power profiles not enabled on this system!"); + } + + boolean changed = false; + try { + if (checkService()) { + changed = sService.setPowerProfile(profile); + } + } catch (RemoteException e) { + throw new IllegalArgumentException(e); + } + return changed; + } + + /** + * Gets the current power profile + * + * Returns null if power profiles are not enabled + */ + public int getPowerProfile() { + int ret = -1; + if (mNumberOfProfiles > 0) { + try { + if (checkService()) { + ret = sService.getPowerProfile(); + } + } catch (RemoteException e) { + // nothing + } + } + return ret; + } + + /** + * Check if profile has app-specific profiles + * + * Returns true if profile has app-specific profiles. + */ + public boolean getProfileHasAppProfiles(int profile) { + boolean ret = false; + if (mNumberOfProfiles > 0) { + try { + if (checkService()) { + ret = sService.getProfileHasAppProfiles(profile); + } + } catch (RemoteException e) { + // nothing + } + } + return ret; + } +} diff --git a/sdk/src/java/cyanogenmod/power/PerformanceManagerInternal.java b/sdk/src/java/cyanogenmod/power/PerformanceManagerInternal.java new file mode 100644 index 0000000..67f158a --- /dev/null +++ b/sdk/src/java/cyanogenmod/power/PerformanceManagerInternal.java @@ -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.power; + +import android.content.Intent; + +/** {@hide} */ +public interface PerformanceManagerInternal { + + void activityResumed(Intent intent); + + void cpuBoost(int duration); + + void launchBoost(); +} diff --git a/sdk/src/java/cyanogenmod/profiles/AirplaneModeSettings.java b/sdk/src/java/cyanogenmod/profiles/AirplaneModeSettings.java new file mode 100644 index 0000000..a9b828f --- /dev/null +++ b/sdk/src/java/cyanogenmod/profiles/AirplaneModeSettings.java @@ -0,0 +1,224 @@ +/* + * 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.profiles; + +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; +import android.os.Parcel; +import android.os.Parcelable; + +import cyanogenmod.os.Build; +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + + +import java.io.IOException; + +/** + * The {@link AirplaneModeSettings} class allows for overriding and setting the airplane mode. + * + * <p>Example for setting the airplane mode to enabled: + * <pre class="prettyprint"> + * AirplaneModeSettings airplaneMode = new AirplaneModeSettings(BooleanState.STATE_ENABLED, true) + * profile.setAirplaneMode(airplaneMode); + * </pre> + */ +public final class AirplaneModeSettings implements Parcelable { + + private int mValue; + private boolean mOverride; + private boolean mDirty; + + /** @hide */ + public static final Parcelable.Creator<AirplaneModeSettings> CREATOR = + new Parcelable.Creator<AirplaneModeSettings>() { + public AirplaneModeSettings createFromParcel(Parcel in) { + return new AirplaneModeSettings(in); + } + + @Override + public AirplaneModeSettings[] newArray(int size) { + return new AirplaneModeSettings[size]; + } + }; + + /** + * BooleanStates for specific {@link AirplaneModeSettings} + */ + public static class BooleanState { + /** Disabled state */ + public static final int STATE_DISALED = 0; + /** Enabled state */ + public static final int STATE_ENABLED = 1; + } + + /** + * Unwrap {@link AirplaneModeSettings} from a parcel. + * @param parcel + */ + public AirplaneModeSettings(Parcel parcel) { + readFromParcel(parcel); + } + + /** + * Construct a {@link AirplaneModeSettings} with a default value of + * {@link BooleanState#STATE_DISALED}. + */ + public AirplaneModeSettings() { + this(BooleanState.STATE_DISALED, false); + } + + /** + * Construct a {@link AirplaneModeSettings} with a default value and whether or not it should + * override user settings. + * @param value ex: {@link BooleanState#STATE_DISALED} + * @param override whether or not the setting should override user settings + */ + public AirplaneModeSettings(int value, boolean override) { + mValue = value; + mOverride = override; + mDirty = false; + } + + /** + * Get the default value for the {@link AirplaneModeSettings} + * @return integer value corresponding with its brightness value + */ + public int getValue() { + return mValue; + } + + /** + * Set the default value for the {@link AirplaneModeSettings} + * @param value {@link BooleanState#STATE_DISALED} + */ + public void setValue(int value) { + mValue = value; + mDirty = true; + } + + /** + * Set whether or not the {@link AirplaneModeSettings} should override default user values + * @param override boolean override + */ + public void setOverride(boolean override) { + mOverride = override; + mDirty = true; + } + + /** + * Check whether or not the {@link AirplaneModeSettings} overrides user settings. + * @return true if override + */ + public boolean isOverride() { + return mOverride; + } + + /** @hide */ + public boolean isDirty() { + return mDirty; + } + + /** @hide */ + public void processOverride(Context context) { + if (isOverride()) { + int current = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0); + if (current != mValue) { + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, mValue); + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", mValue == 1); + context.sendBroadcast(intent); + } + } + } + + /** @hide */ + public static AirplaneModeSettings fromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, IOException { + int event = xpp.next(); + AirplaneModeSettings airplaneModeDescriptor = new AirplaneModeSettings(); + while ((event != XmlPullParser.END_TAG && event != XmlPullParser.END_DOCUMENT) || + !xpp.getName().equals("airplaneModeDescriptor")) { + if (event == XmlPullParser.START_TAG) { + String name = xpp.getName(); + if (name.equals("value")) { + airplaneModeDescriptor.mValue = Integer.parseInt(xpp.nextText()); + } else if (name.equals("override")) { + airplaneModeDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText()); + } + } else if (event == XmlPullParser.END_DOCUMENT) { + throw new IOException("Premature end of file while parsing airplane mode settings"); + } + event = xpp.next(); + } + return airplaneModeDescriptor; + } + + /** @hide */ + public void getXmlString(StringBuilder builder, Context context) { + builder.append("<airplaneModeDescriptor>\n<value>"); + builder.append(mValue); + builder.append("</value>\n<override>"); + builder.append(mOverride); + builder.append("</override>\n</airplaneModeDescriptor>\n"); + } + + @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.writeInt(mOverride ? 1 : 0); + dest.writeInt(mValue); + dest.writeInt(mDirty ? 1 : 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) { + mOverride = in.readInt() != 0; + mValue = in.readInt(); + mDirty = in.readInt() != 0; + } + + // Complete parcel info for the concierge + parcelInfo.complete(); + } +} diff --git a/sdk/src/java/cyanogenmod/profiles/BrightnessSettings.java b/sdk/src/java/cyanogenmod/profiles/BrightnessSettings.java new file mode 100644 index 0000000..0786a72 --- /dev/null +++ b/sdk/src/java/cyanogenmod/profiles/BrightnessSettings.java @@ -0,0 +1,222 @@ +/* + * 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.profiles; + +import android.content.Context; +import android.provider.Settings; +import android.os.Parcel; +import android.os.Parcelable; + +import cyanogenmod.os.Build; +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + + +import java.io.IOException; + +/** + * The {@link BrightnessSettings} class allows for overriding and setting the brightness level + * of the display. The range for brightness is between 0 -> 255. + * + * <p>Example for setting the brightness to ~25% (255 * .25): + * <pre class="prettyprint"> + * BrightnessSettings twentyFivePercent = new BrightnessSettings(63, true) + * profile.setBrightness(twentyFivePercent); + * </pre> + */ +public final class BrightnessSettings implements Parcelable { + + private int mValue; + private boolean mOverride; + private boolean mDirty; + + /** @hide */ + public static final Parcelable.Creator<BrightnessSettings> CREATOR + = new Parcelable.Creator<BrightnessSettings>() { + public BrightnessSettings createFromParcel(Parcel in) { + return new BrightnessSettings(in); + } + + @Override + public BrightnessSettings[] newArray(int size) { + return new BrightnessSettings[size]; + } + }; + + /** + * Unwrap {@link BrightnessSettings} from a parcel. + * @param parcel + */ + public BrightnessSettings(Parcel parcel) { + readFromParcel(parcel); + } + + /** + * Construct a {@link BrightnessSettings} with a default value of 0. + */ + public BrightnessSettings() { + this(0, false); + } + + /** + * Construct a {@link BrightnessSettings} with a default value and whether or not it should + * override user settings. + * @param value ex: 255 (MAX) + * @param override whether or not the setting should override user settings + */ + public BrightnessSettings(int value, boolean override) { + mValue = value; + mOverride = override; + mDirty = false; + } + + /** + * Get the default value for the {@link BrightnessSettings} + * @return integer value corresponding with its brightness value + */ + public int getValue() { + return mValue; + } + + /** + * Set the default value for the {@link BrightnessSettings} + * @param value ex: 255 (MAX) + */ + public void setValue(int value) { + mValue = value; + mDirty = true; + } + + /** + * Set whether or not the {@link BrightnessSettings} should override default user values + * @param override boolean override + */ + public void setOverride(boolean override) { + mOverride = override; + mDirty = true; + } + + /** + * Check whether or not the {@link BrightnessSettings} overrides user settings. + * @return true if override + */ + public boolean isOverride() { + return mOverride; + } + + /** @hide */ + public boolean isDirty() { + return mDirty; + } + + /** @hide */ + public void processOverride(Context context) { + if (isOverride()) { + final boolean automatic = Settings.System.getInt(context.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) + == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; + if (automatic) { + final float current = Settings.System.getFloat(context.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, -2f); + // Convert from [0, 255] to [-1, 1] for SCREEN_AUTO_BRIGHTNESS_ADJ + final float adj = mValue / (255 / 2f) - 1; + if (current != adj) { + Settings.System.putFloat(context.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adj); + } + } else { + final int current = Settings.System.getInt(context.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, -1); + if (current != mValue) { + Settings.System.putInt(context.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, mValue); + } + } + } + } + + /** @hide */ + public static BrightnessSettings fromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, IOException { + int event = xpp.next(); + BrightnessSettings brightnessDescriptor = new BrightnessSettings(); + while (event != XmlPullParser.END_TAG || !xpp.getName().equals("brightnessDescriptor")) { + if (event == XmlPullParser.START_TAG) { + String name = xpp.getName(); + if (name.equals("value")) { + brightnessDescriptor.mValue = Integer.parseInt(xpp.nextText()); + } else if (name.equals("override")) { + brightnessDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText()); + } + } + event = xpp.next(); + } + return brightnessDescriptor; + } + + /** @hide */ + public void getXmlString(StringBuilder builder, Context context) { + builder.append("<brightnessDescriptor>\n<value>"); + builder.append(mValue); + builder.append("</value>\n<override>"); + builder.append(mOverride); + builder.append("</override>\n</brightnessDescriptor>\n"); + } + + @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.writeInt(mOverride ? 1 : 0); + dest.writeInt(mValue); + dest.writeInt(mDirty ? 1 : 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) { + mOverride = in.readInt() != 0; + mValue = in.readInt(); + mDirty = in.readInt() != 0; + } + + // Complete parcel info for the concierge + parcelInfo.complete(); + } +} diff --git a/sdk/src/java/cyanogenmod/profiles/ConnectionSettings.java b/sdk/src/java/cyanogenmod/profiles/ConnectionSettings.java new file mode 100644 index 0000000..19954dd --- /dev/null +++ b/sdk/src/java/cyanogenmod/profiles/ConnectionSettings.java @@ -0,0 +1,475 @@ +/* + * 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.profiles; + +import android.bluetooth.BluetoothAdapter; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.location.LocationManager; +import android.net.wifi.WifiManager; +import android.net.wimax.WimaxHelper; +import android.nfc.NfcAdapter; +import android.os.Parcel; +import android.os.Parcelable; +import android.provider.Settings; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import com.android.internal.telephony.RILConstants; + +import cyanogenmod.os.Build; +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * The {@link ConnectionSettings} class allows for creating Network/Hardware overrides + * depending on their capabilities. + * + * <p>Example for enabling/disabling sync settings: + * <pre class="prettyprint"> + * ConnectionSettings connectionSettings = + * new ConnectionSettings(ConnectionSettings.PROFILE_CONNECTION_SYNC, + * shouldBeEnabled() ? + * {@link BooleanState#STATE_ENABLED} : {@link BooleanState#STATE_DISALED}, + * true) + * profile.setConnectionSettings(connectionSettings); + * </pre> + */ +public final class ConnectionSettings implements Parcelable { + + private int mConnectionId; + private int mValue; + private boolean mOverride; + private boolean mDirty; + + /** + * For use with {@link #PROFILE_CONNECTION_2G3G4G} to determine what subscription to control. + */ + private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + + /** + * The {@link #PROFILE_CONNECTION_MOBILEDATA} allows for enabling and disabling the mobile + * data connection. Boolean connection settings {@link BooleanState} + */ + public static final int PROFILE_CONNECTION_MOBILEDATA = 0; + + /** + * The {@link #PROFILE_CONNECTION_WIFI} allows for enabling and disabling the WiFi connection + * on the device. Boolean connection settings {@link BooleanState} + */ + public static final int PROFILE_CONNECTION_WIFI = 1; + + /** + * The {@link #PROFILE_CONNECTION_WIFIAP} allows for enabling and disabling the WiFi hotspot + * on the device. Boolean connection settings {@link BooleanState} + */ + public static final int PROFILE_CONNECTION_WIFIAP = 2; + + /** + * The {@link #PROFILE_CONNECTION_WIMAX} allows for enabling and disabling the WIMAX radio (if exists) + * on the device. Boolean connection settings {@link BooleanState} + */ + public static final int PROFILE_CONNECTION_WIMAX = 3; + + /** + * The {@link #PROFILE_CONNECTION_GPS} allows for enabling and disabling the GPS radio (if exists) + * on the device. Boolean connection settings {@link BooleanState} + */ + public static final int PROFILE_CONNECTION_GPS = 4; + + /** + * The {@link #PROFILE_CONNECTION_SYNC} allows for enabling and disabling the global sync state + * on the device. Boolean connection settings {@link BooleanState} + */ + public static final int PROFILE_CONNECTION_SYNC = 5; + + /** + * The {@link #PROFILE_CONNECTION_BLUETOOTH} allows for enabling and disabling the Bluetooth device + * (if exists) on the device. Boolean connection settings {@link BooleanState} + */ + public static final int PROFILE_CONNECTION_BLUETOOTH = 7; + + /** + * The {@link #PROFILE_CONNECTION_NFC} allows for enabling and disabling the NFC device + * (if exists) on the device. Boolean connection settings {@link BooleanState} + */ + public static final int PROFILE_CONNECTION_NFC = 8; + + /** + * The {@link #PROFILE_CONNECTION_2G3G4G} allows for flipping between 2G/3G/4G (if exists) + * on the device. + */ + public static final int PROFILE_CONNECTION_2G3G4G = 9; + + // retrieved from Phone.apk + private static final String ACTION_MODIFY_NETWORK_MODE = + "com.android.internal.telephony.MODIFY_NETWORK_MODE"; + private static final String EXTRA_NETWORK_MODE = "networkMode"; + private static final String EXTRA_SUB_ID = "subId"; + + /** + * BooleanStates for specific {@link ConnectionSettings} + */ + public static class BooleanState { + /** Disabled state */ + public static final int STATE_DISALED = 0; + /** Enabled state */ + public static final int STATE_ENABLED = 1; + } + + private static final int CM_MODE_2G = 0; + private static final int CM_MODE_3G = 1; + private static final int CM_MODE_4G = 2; + private static final int CM_MODE_2G3G = 3; + private static final int CM_MODE_ALL = 4; + + /** @hide */ + public static final Parcelable.Creator<ConnectionSettings> CREATOR = + new Parcelable.Creator<ConnectionSettings>() { + public ConnectionSettings createFromParcel(Parcel in) { + return new ConnectionSettings(in); + } + + @Override + public ConnectionSettings[] newArray(int size) { + return new ConnectionSettings[size]; + } + }; + + /** + * Unwrap {@link ConnectionSettings} from a parcel. + * @param parcel + */ + public ConnectionSettings(Parcel parcel) { + readFromParcel(parcel); + } + + /** + * Construct a {@link ConnectionSettings} with a connection id and default states. + * @param connectionId ex: #PROFILE_CONNECTION_NFC + */ + public ConnectionSettings(int connectionId) { + this(connectionId, 0, false); + } + + /** + * Construct a {@link ConnectionSettings} with a connection id, default value + * {@see BooleanState}, and if the setting should override the user defaults. + * @param connectionId an identifier for the ConnectionSettings (ex:#PROFILE_CONNECTION_WIFI) + * @param value default value for the ConnectionSettings (ex:{@link BooleanState#STATE_ENABLED}) + * @param override whether or not the {@link ConnectionSettings} should override user defaults + */ + public ConnectionSettings(int connectionId, int value, boolean override) { + mConnectionId = connectionId; + mValue = value; + mOverride = override; + mDirty = false; + } + + /** + * Retrieve the connection id associated with the {@link ConnectionSettings} + * @return an integer identifier + */ + public int getConnectionId() { + return mConnectionId; + } + + /** + * Get the default value for the {@link ConnectionSettings} + * @return integer value corresponding with its state + */ + public int getValue() { + return mValue; + } + + /** + * Set the default value for the {@link ConnectionSettings} + * @param value {@link BooleanState} + */ + public void setValue(int value) { + mValue = value; + mDirty = true; + } + + /** + * Set whether or not the {@link ConnectionSettings} should override default user values + * @param override boolean override + */ + public void setOverride(boolean override) { + mOverride = override; + mDirty = true; + } + + public void setSubId(int subId) { + mSubId = subId; + mDirty = true; + } + + /** + * Check whether or not the {@link ConnectionSettings} overrides user settings. + * @return true if override + */ + public boolean isOverride() { + return mOverride; + } + + /** + * Get the subscription id which this {@link ConnectionSettings} should apply to. + * @return + */ + public int getSubId() { + return mSubId; + } + + /** @hide */ + public boolean isDirty() { + return mDirty; + } + + /** @hide */ + public void processOverride(Context context) { + BluetoothAdapter bta = BluetoothAdapter.getDefaultAdapter(); + LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + TelephonyManager tm = (TelephonyManager) + context.getSystemService(Context.TELEPHONY_SERVICE); + NfcAdapter nfcAdapter = null; + try { + nfcAdapter = NfcAdapter.getNfcAdapter(context); + } catch (UnsupportedOperationException e) { + //Nfc not available + } + + boolean forcedState = getValue() == 1; + boolean currentState; + + switch (getConnectionId()) { + case PROFILE_CONNECTION_MOBILEDATA: + currentState = tm.getDataEnabled(); + if (forcedState != currentState) { + int phoneCount = tm.getPhoneCount(); + for (int i = 0; i < phoneCount; i++) { + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.MOBILE_DATA + i, (forcedState) ? 1 : 0); + int[] subId = SubscriptionManager.getSubId(i); + tm.setDataEnabled(subId[0], forcedState); + } + } + break; + case PROFILE_CONNECTION_2G3G4G: + if (Build.CM_VERSION.SDK_INT >= Build.CM_VERSION_CODES.ELDERBERRY) { + Intent intent = new Intent(ACTION_MODIFY_NETWORK_MODE); + intent.putExtra(EXTRA_NETWORK_MODE, getValue()); + intent.putExtra(EXTRA_SUB_ID, getSubId()); + context.sendBroadcast(intent, "com.android.phone.CHANGE_NETWORK_MODE"); + } else { + Intent intent = new Intent(ACTION_MODIFY_NETWORK_MODE); + switch(getValue()) { + case CM_MODE_2G: + intent.putExtra(EXTRA_NETWORK_MODE, RILConstants.NETWORK_MODE_GSM_ONLY); + break; + case CM_MODE_3G: + intent.putExtra(EXTRA_NETWORK_MODE, RILConstants.NETWORK_MODE_WCDMA_ONLY); + break; + case CM_MODE_4G: + intent.putExtra(EXTRA_NETWORK_MODE, RILConstants.NETWORK_MODE_LTE_ONLY); + break; + case CM_MODE_2G3G: + intent.putExtra(EXTRA_NETWORK_MODE, RILConstants.NETWORK_MODE_WCDMA_PREF); + break; + case CM_MODE_ALL: + intent.putExtra(EXTRA_NETWORK_MODE, + RILConstants.NETWORK_MODE_LTE_GSM_WCDMA); + break; + default: + return; + } + context.sendBroadcast(intent); + } + break; + case PROFILE_CONNECTION_BLUETOOTH: + int btstate = bta.getState(); + if (forcedState && (btstate == BluetoothAdapter.STATE_OFF + || btstate == BluetoothAdapter.STATE_TURNING_OFF)) { + bta.enable(); + } else if (!forcedState && (btstate == BluetoothAdapter.STATE_ON + || btstate == BluetoothAdapter.STATE_TURNING_ON)) { + bta.disable(); + } + break; + case PROFILE_CONNECTION_GPS: + currentState = lm.isProviderEnabled(LocationManager.GPS_PROVIDER); + if (currentState != forcedState) { + Settings.Secure.setLocationProviderEnabled(context.getContentResolver(), + LocationManager.GPS_PROVIDER, forcedState); + } + break; + case PROFILE_CONNECTION_SYNC: + currentState = ContentResolver.getMasterSyncAutomatically(); + if (forcedState != currentState) { + ContentResolver.setMasterSyncAutomatically(forcedState); + } + break; + case PROFILE_CONNECTION_WIFI: + int wifiApState = wm.getWifiApState(); + currentState = wm.isWifiEnabled(); + if (currentState != forcedState) { + // Disable wifi tether + if (forcedState && (wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || + (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED)) { + wm.setWifiApEnabled(null, false); + } + wm.setWifiEnabled(forcedState); + } + break; + case PROFILE_CONNECTION_WIFIAP: + int wifiState = wm.getWifiState(); + currentState = wm.isWifiApEnabled(); + if (currentState != forcedState) { + // Disable wifi + if (forcedState && (wifiState == WifiManager.WIFI_STATE_ENABLING) || + (wifiState == WifiManager.WIFI_STATE_ENABLED)) { + wm.setWifiEnabled(false); + } + wm.setWifiApEnabled(null, forcedState); + } + break; + case PROFILE_CONNECTION_WIMAX: + if (WimaxHelper.isWimaxSupported(context)) { + currentState = WimaxHelper.isWimaxEnabled(context); + if (currentState != forcedState) { + WimaxHelper.setWimaxEnabled(context, forcedState); + } + } + break; + case PROFILE_CONNECTION_NFC: + if (nfcAdapter != null) { + int adapterState = nfcAdapter.getAdapterState(); + currentState = (adapterState == NfcAdapter.STATE_ON || + adapterState == NfcAdapter.STATE_TURNING_ON); + if (currentState != forcedState) { + if (forcedState) { + nfcAdapter.enable(); + } else if (!forcedState && adapterState != NfcAdapter.STATE_TURNING_OFF) { + nfcAdapter.disable(); + } + } + } + break; + } + } + + /** @hide */ + public static ConnectionSettings fromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, IOException { + int event = xpp.next(); + ConnectionSettings connectionDescriptor = new ConnectionSettings(0); + while (event != XmlPullParser.END_TAG || !xpp.getName().equals("connectionDescriptor")) { + if (event == XmlPullParser.START_TAG) { + String name = xpp.getName(); + if (name.equals("connectionId")) { + connectionDescriptor.mConnectionId = Integer.parseInt(xpp.nextText()); + } else if (name.equals("value")) { + connectionDescriptor.mValue = Integer.parseInt(xpp.nextText()); + } else if (name.equals("override")) { + connectionDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText()); + } else if (name.equals("subId")) { + connectionDescriptor.mSubId = Integer.parseInt(xpp.nextText()); + } + } else if (event == XmlPullParser.END_DOCUMENT) { + throw new IOException("Premature end of file while parsing connection settings"); + } + event = xpp.next(); + } + return connectionDescriptor; + } + + /** @hide */ + public void getXmlString(StringBuilder builder, Context context) { + builder.append("<connectionDescriptor>\n<connectionId>"); + builder.append(mConnectionId); + builder.append("</connectionId>\n<value>"); + builder.append(mValue); + builder.append("</value>\n<override>"); + builder.append(mOverride); + builder.append("</override>\n"); + if (Build.CM_VERSION.SDK_INT >= Build.CM_VERSION_CODES.ELDERBERRY) { + if (mConnectionId == PROFILE_CONNECTION_2G3G4G + && mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + builder.append("<subId>").append(mSubId).append("</subId>\n"); + } + } + builder.append("</connectionDescriptor>\n"); + } + + @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.writeInt(mConnectionId); + dest.writeInt(mOverride ? 1 : 0); + dest.writeInt(mValue); + dest.writeInt(mDirty ? 1 : 0); + + // === ELDERBERRY === + if (mConnectionId == PROFILE_CONNECTION_2G3G4G) { + dest.writeInt(mSubId); + } + + // 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) { + mConnectionId = in.readInt(); + mOverride = in.readInt() != 0; + mValue = in.readInt(); + mDirty = in.readInt() != 0; + } + + if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) { + if (mConnectionId == PROFILE_CONNECTION_2G3G4G) { + mSubId = in.readInt(); + } + } + + // Complete parcel info for the concierge + parcelInfo.complete(); + } +} diff --git a/sdk/src/java/cyanogenmod/profiles/LockSettings.java b/sdk/src/java/cyanogenmod/profiles/LockSettings.java new file mode 100644 index 0000000..2be3725 --- /dev/null +++ b/sdk/src/java/cyanogenmod/profiles/LockSettings.java @@ -0,0 +1,179 @@ +/* + * 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.profiles; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.util.Log; +/* import android.view.WindowManagerPolicyControl; */ +import com.android.internal.policy.IKeyguardService; +/* import com.android.internal.policy.PolicyManager; */ + +import cyanogenmod.app.Profile; +import cyanogenmod.os.Build; +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; + +/** + * The {@link LockSettings} class allows for overriding and setting the + * current Lock screen state/security level. Value should be one a constant from + * of {@link Profile.LockMode} + * + * <p>Example for disabling lockscreen security: + * <pre class="prettyprint"> + * LockSettings lock = new LockSettings(Profile.LockMode.INSECURE); + * profile.setScreenLockMode(lock); + * </pre> + */ +public final class LockSettings implements Parcelable { + + private static final String TAG = LockSettings.class.getSimpleName(); + + private int mValue; + private boolean mDirty; + + /** @hide */ + public static final Creator<LockSettings> CREATOR + = new Creator<LockSettings>() { + public LockSettings createFromParcel(Parcel in) { + return new LockSettings(in); + } + + @Override + public LockSettings[] newArray(int size) { + return new LockSettings[size]; + } + }; + + /** + * Unwrap {@link LockSettings} from a parcel. + * @param parcel + */ + public LockSettings(Parcel parcel) { + readFromParcel(parcel); + } + + /** + * Construct a {@link LockSettings} with a default value of {@link Profile.LockMode.DEFAULT}. + */ + public LockSettings() { + this(Profile.LockMode.DEFAULT); + } + + /** + * Construct a {@link LockSettings} with a default value. + */ + public LockSettings(int value) { + mValue = value; + mDirty = false; + } + + /** + * Get the value for the {@link LockSettings} + * @return a constant from {@link Profile.LockMode} + */ + public int getValue() { + return mValue; + } + + /** + * Set the value for the {@link LockSettings} + * + * see {@link Profile.LockMode} + */ + public void setValue(int value) { + mValue = value; + mDirty = true; + } + + /** @hide */ + public boolean isDirty() { + return mDirty; + } + + /** @hide */ + public void processOverride(Context context, IKeyguardService keyguard) { + boolean enable; + final DevicePolicyManager devicePolicyManager = + (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); + if (devicePolicyManager != null && devicePolicyManager.requireSecureKeyguard()) { + enable = true; + } else { + switch (mValue) { + default: + case Profile.LockMode.DEFAULT: + case Profile.LockMode.INSECURE: + enable = true; + break; + case Profile.LockMode.DISABLE: + enable = false; + break; + } + } + + try { + keyguard.setKeyguardEnabled(enable); + } catch (RemoteException e) { + Log.w(TAG, "unable to set keyguard enabled state to: " + enable, e); + } + } + + /** @hide */ + public void writeXmlString(StringBuilder builder, Context context) { + builder.append(mValue); + } + + @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.writeInt(mValue); + dest.writeInt(mDirty ? 1 : 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) { + mValue = in.readInt(); + mDirty = in.readInt() != 0; + } + + // Complete parcel info for the concierge + parcelInfo.complete(); + } +} diff --git a/sdk/src/java/cyanogenmod/profiles/RingModeSettings.java b/sdk/src/java/cyanogenmod/profiles/RingModeSettings.java new file mode 100644 index 0000000..bab74ac --- /dev/null +++ b/sdk/src/java/cyanogenmod/profiles/RingModeSettings.java @@ -0,0 +1,214 @@ +/* + * 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.profiles; + +import android.content.Context; +import android.media.AudioManager; +import android.os.Parcel; +import android.os.Parcelable; + +import cyanogenmod.os.Build; +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * The {@link StreamSettings} class allows for creating various {@link android.media.AudioManager} + * overrides on the device depending on their capabilities. + * + * <p>Example for setting the default ring mode to muted: + * <pre class="prettyprint"> + * RingModeSettings ringSettings = new RingModeSettings(RING_MODE_MUTE, true)); + * profile.setRingMode(ringSettings); + * </pre> + */ +public final class RingModeSettings implements Parcelable { + public static final String RING_MODE_NORMAL = "normal"; + public static final String RING_MODE_VIBRATE = "vibrate"; + public static final String RING_MODE_MUTE = "mute"; + + private String mValue; + private boolean mOverride; + private boolean mDirty; + + /** @hide */ + public static final Parcelable.Creator<RingModeSettings> CREATOR + = new Parcelable.Creator<RingModeSettings>() { + public RingModeSettings createFromParcel(Parcel in) { + return new RingModeSettings(in); + } + + @Override + public RingModeSettings[] newArray(int size) { + return new RingModeSettings[size]; + } + }; + + /** + * Unwrap {@link RingModeSettings} from a parcel. + * @param parcel + */ + public RingModeSettings(Parcel parcel) { + readFromParcel(parcel); + } + + /** + * Construct a {@link RingModeSettings} with a default state of #RING_MODE_NORMAL. + */ + public RingModeSettings() { + this(RING_MODE_NORMAL, false); + } + + /** + * Construct a {@link RingModeSettings} with a default value and whether or not it should + * override user settings. + * @param value ex: {@link #RING_MODE_VIBRATE} + * @param override whether or not the setting should override user settings + */ + public RingModeSettings(String value, boolean override) { + mValue = value; + mOverride = override; + mDirty = false; + } + + /** + * Get the default value for the {@link RingModeSettings} + * @return integer value corresponding with its type + */ + public String getValue() { + return mValue; + } + + /** + * Set the default value for the {@link RingModeSettings} + * @param value ex: {@link #RING_MODE_VIBRATE} + */ + public void setValue(String value) { + mValue = value; + mDirty = true; + } + + /** + * Set whether or not the {@link RingModeSettings} should override default user values + * @param override boolean override + */ + public void setOverride(boolean override) { + mOverride = override; + mDirty = true; + } + + /** + * Check whether or not the {@link RingModeSettings} overrides user settings. + * @return true if override + */ + public boolean isOverride() { + return mOverride; + } + + /** @hide */ + public boolean isDirty() { + return mDirty; + } + + /** @hide */ + public void processOverride(Context context) { + if (isOverride()) { + int ringerMode = AudioManager.RINGER_MODE_NORMAL; + if (mValue.equals(RING_MODE_MUTE)) { + ringerMode = AudioManager.RINGER_MODE_SILENT; + } else if (mValue.equals(RING_MODE_VIBRATE)) { + ringerMode = AudioManager.RINGER_MODE_VIBRATE; + } + AudioManager amgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + amgr.setRingerModeInternal(ringerMode); + } + } + + /** @hide */ + public static RingModeSettings fromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, IOException { + int event = xpp.next(); + RingModeSettings ringModeDescriptor = new RingModeSettings(); + while ((event != XmlPullParser.END_TAG && event != XmlPullParser.END_DOCUMENT) || + !xpp.getName().equals("ringModeDescriptor")) { + if (event == XmlPullParser.START_TAG) { + String name = xpp.getName(); + if (name.equals("value")) { + ringModeDescriptor.mValue = xpp.nextText(); + } else if (name.equals("override")) { + ringModeDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText()); + } + } else if (event == XmlPullParser.END_DOCUMENT) { + throw new IOException("Premature end of file while parsing ring mode settings"); + } + event = xpp.next(); + } + return ringModeDescriptor; + } + + /** @hide */ + public void getXmlString(StringBuilder builder, Context context) { + builder.append("<ringModeDescriptor>\n<value>"); + builder.append(mValue); + builder.append("</value>\n<override>"); + builder.append(mOverride); + builder.append("</override>\n</ringModeDescriptor>\n"); + } + + @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.writeInt(mOverride ? 1 : 0); + dest.writeString(mValue); + dest.writeInt(mDirty ? 1 : 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) { + mOverride = in.readInt() != 0; + mValue = in.readString(); + mDirty = in.readInt() != 0; + } + + // Complete parcel info for the concierge + parcelInfo.complete(); + } +} diff --git a/sdk/src/java/cyanogenmod/profiles/StreamSettings.java b/sdk/src/java/cyanogenmod/profiles/StreamSettings.java new file mode 100644 index 0000000..9ce9415 --- /dev/null +++ b/sdk/src/java/cyanogenmod/profiles/StreamSettings.java @@ -0,0 +1,216 @@ +/* + * 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.profiles; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; + + +import cyanogenmod.os.Build; +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; + +import java.io.IOException; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +/** + * The {@link StreamSettings} class allows for creating various {@link android.media.AudioManager} + * overrides on the device depending on their capabilities. + * + * <p>Example for setting the alarm stream defaults and override: + * <pre class="prettyprint"> + * StreamSettings alarmStreamSettings = new StreamSettings(AudioManager.STREAM_ALARM, + * am.getStreamVolume(AudioManager.STREAM_ALARM), true)); + * profile.setStreamSettings(alarmStreamSettings); + * </pre> + */ +public final class StreamSettings implements Parcelable{ + + private int mStreamId; + private int mValue; + private boolean mOverride; + private boolean mDirty; + + /** @hide */ + public static final Parcelable.Creator<StreamSettings> CREATOR = + new Parcelable.Creator<StreamSettings>() { + public StreamSettings createFromParcel(Parcel in) { + return new StreamSettings(in); + } + + @Override + public StreamSettings[] newArray(int size) { + return new StreamSettings[size]; + } + }; + + /** + * Unwrap {@link StreamSettings} from a parcel. + * @param parcel + */ + public StreamSettings(Parcel parcel) { + readFromParcel(parcel); + } + + /** + * Construct a {@link StreamSettings} with a stream id and default states. + * @param streamId ex: {@link android.media.AudioManager#STREAM_ALARM} + */ + public StreamSettings(int streamId) { + this(streamId, 0, false); + } + + /** + * Construct a {@link StreamSettings} with a stream id, default value, + * and if the setting should override the user defaults. + * @param streamId ex: {@link android.media.AudioManager#STREAM_ALARM} + * @param value default value for the {@link StreamSettings} + * @param override whether or not the {@link StreamSettings} should override user defaults + */ + public StreamSettings(int streamId, int value, boolean override) { + mStreamId = streamId; + mValue = value; + mOverride = override; + mDirty = false; + } + + /** + * Retrieve the stream id id associated with the {@link StreamSettings} + * @return an integer identifier + */ + public int getStreamId() { + return mStreamId; + } + + /** + * Get the default value for the {@link StreamSettings} + * @return integer value corresponding with its state + */ + public int getValue() { + return mValue; + } + + /** + * Set the default value for the {@link StreamSettings} + * @param value see {@link android.media.AudioManager} for viable values + */ + public void setValue(int value) { + mValue = value; + mDirty = true; + } + + /** + * Set whether or not the {@link StreamSettings} should override default user values + * @param override boolean override + */ + public void setOverride(boolean override) { + mOverride = override; + mDirty = true; + } + + /** + * Check whether or not the {@link StreamSettings} overrides user settings. + * @return true if override + */ + public boolean isOverride() { + return mOverride; + } + + /** @hide */ + public boolean isDirty() { + return mDirty; + } + + /** @hide */ + public static StreamSettings fromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, IOException { + int event = xpp.next(); + StreamSettings streamDescriptor = new StreamSettings(0); + while (event != XmlPullParser.END_TAG || !xpp.getName().equals("streamDescriptor")) { + if (event == XmlPullParser.START_TAG) { + String name = xpp.getName(); + if (name.equals("streamId")) { + streamDescriptor.mStreamId = Integer.parseInt(xpp.nextText()); + } else if (name.equals("value")) { + streamDescriptor.mValue = Integer.parseInt(xpp.nextText()); + } else if (name.equals("override")) { + streamDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText()); + } + } else if (event == XmlPullParser.END_DOCUMENT) { + throw new IOException("Premature end of file while parsing stream settings"); + } + event = xpp.next(); + } + return streamDescriptor; + } + + /** @hide */ + public void getXmlString(StringBuilder builder, Context context) { + builder.append("<streamDescriptor>\n<streamId>"); + builder.append(mStreamId); + builder.append("</streamId>\n<value>"); + builder.append(mValue); + builder.append("</value>\n<override>"); + builder.append(mOverride); + builder.append("</override>\n</streamDescriptor>\n"); + mDirty = false; + } + + @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.writeInt(mStreamId); + dest.writeInt(mOverride ? 1 : 0); + dest.writeInt(mValue); + dest.writeInt(mDirty ? 1 : 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) { + mStreamId = in.readInt(); + mOverride = in.readInt() != 0; + mValue = in.readInt(); + mDirty = in.readInt() != 0; + } + + // Complete parcel info for the concierge + parcelInfo.complete(); + } +} diff --git a/sdk/src/java/cyanogenmod/providers/CMSettings.java b/sdk/src/java/cyanogenmod/providers/CMSettings.java new file mode 100644 index 0000000..47cc524 --- /dev/null +++ b/sdk/src/java/cyanogenmod/providers/CMSettings.java @@ -0,0 +1,3258 @@ +/** + * 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.providers; + +import com.android.internal.util.ArrayUtils; + +import android.content.ContentResolver; +import android.content.IContentProvider; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.AndroidException; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; + +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * CMSettings contains CM specific preferences in System, Secure, and Global. + */ +public final class CMSettings { + private static final String TAG = "CMSettings"; + private static final boolean LOCAL_LOGV = false; + + public static final String AUTHORITY = "cmsettings"; + + public static class CMSettingNotFoundException extends AndroidException { + public CMSettingNotFoundException(String msg) { + super(msg); + } + } + + // Intent actions for Settings + /** + * Activity Action: Show Data Usage Summary + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + public static final String ACTION_DATA_USAGE = "cyanogenmod.settings.ACTION_DATA_USAGE"; + + // region Call Methods + + /** + * @hide - User handle argument extra to the fast-path call()-based requests + */ + public static final String CALL_METHOD_USER_KEY = "_user"; + + /** + * @hide - Private call() method on SettingsProvider to read from 'system' table. + */ + public static final String CALL_METHOD_GET_SYSTEM = "GET_system"; + + /** + * @hide - Private call() method on SettingsProvider to read from 'secure' table. + */ + public static final String CALL_METHOD_GET_SECURE = "GET_secure"; + + /** + * @hide - Private call() method on SettingsProvider to read from 'global' table. + */ + public static final String CALL_METHOD_GET_GLOBAL = "GET_global"; + + /** + * @hide - Private call() method to write to 'system' table + */ + public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system"; + + /** + * @hide - Private call() method to write to 'secure' table + */ + public static final String CALL_METHOD_PUT_SECURE = "PUT_secure"; + + /** + * @hide - Private call() method to write to 'global' table + */ + public static final String CALL_METHOD_PUT_GLOBAL= "PUT_global"; + + /** + * @hide - Private call() method on CMSettingsProvider to migrate CM settings + */ + public static final String CALL_METHOD_MIGRATE_SETTINGS = "migrate_settings"; + + /** + * @hide - Private call() method on CMSettingsProvider to migrate CM settings for a user + */ + public static final String CALL_METHOD_MIGRATE_SETTINGS_FOR_USER = "migrate_settings_for_user"; + + // endregion + + // Thread-safe. + private static class NameValueCache { + private final String mVersionSystemProperty; + private final Uri mUri; + + private static final String[] SELECT_VALUE = + new String[] { Settings.NameValueTable.VALUE }; + private static final String NAME_EQ_PLACEHOLDER = "name=?"; + + // Must synchronize on 'this' to access mValues and mValuesVersion. + private final HashMap<String, String> mValues = new HashMap<String, String>(); + private long mValuesVersion = 0; + + // Initially null; set lazily and held forever. Synchronized on 'this'. + private IContentProvider mContentProvider = null; + + // The method we'll call (or null, to not use) on the provider + // for the fast path of retrieving settings. + private final String mCallGetCommand; + private final String mCallSetCommand; + + public NameValueCache(String versionSystemProperty, Uri uri, + String getCommand, String setCommand) { + mVersionSystemProperty = versionSystemProperty; + mUri = uri; + mCallGetCommand = getCommand; + mCallSetCommand = setCommand; + } + + private IContentProvider lazyGetProvider(ContentResolver cr) { + IContentProvider cp; + synchronized (this) { + cp = mContentProvider; + if (cp == null) { + cp = mContentProvider = cr.acquireProvider(mUri.getAuthority()); + } + } + return cp; + } + + /** + * Puts a string name/value pair into the content provider for the specified user. + * @param cr The content resolver to use. + * @param name The name of the key to put into the content provider. + * @param value The value to put into the content provider. + * @param userId The user id to use for the content provider. + * @return Whether the put was successful. + */ + public boolean putStringForUser(ContentResolver cr, String name, String value, + final int userId) { + try { + Bundle arg = new Bundle(); + arg.putString(Settings.NameValueTable.VALUE, value); + arg.putInt(CALL_METHOD_USER_KEY, userId); + IContentProvider cp = lazyGetProvider(cr); + cp.call(cr.getPackageName(), mCallSetCommand, name, arg); + } catch (RemoteException e) { + Log.w(TAG, "Can't set key " + name + " in " + mUri, e); + return false; + } + return true; + } + + /** + * Gets a string value with the specified name from the name/value cache if possible. If + * not, it will use the content resolver and perform a query. + * @param cr Content resolver to use if name/value cache does not contain the name or if + * the cache version is older than the current version. + * @param name The name of the key to search for. + * @param userId The user id of the cache to look in. + * @return The string value of the specified key. + */ + public String getStringForUser(ContentResolver cr, String name, final int userId) { + final boolean isSelf = (userId == UserHandle.myUserId()); + if (isSelf) { + if (LOCAL_LOGV) Log.d(TAG, "get setting for self"); + long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0); + + // Our own user's settings data uses a client-side cache + synchronized (this) { + if (mValuesVersion != newValuesVersion) { + if (LOCAL_LOGV || false) { + Log.v(TAG, "invalidate [" + mUri.getLastPathSegment() + "]: current " + + newValuesVersion + " != cached " + mValuesVersion); + } + + mValues.clear(); + mValuesVersion = newValuesVersion; + } + + if (mValues.containsKey(name)) { + return mValues.get(name); // Could be null, that's OK -- negative caching + } + } + } else { + if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userId + + " by user " + UserHandle.myUserId() + " so skipping cache"); + } + + IContentProvider cp = lazyGetProvider(cr); + + // Try the fast path first, not using query(). If this + // fails (alternate Settings provider that doesn't support + // this interface?) then we fall back to the query/table + // interface. + if (mCallGetCommand != null) { + try { + Bundle args = null; + if (!isSelf) { + args = new Bundle(); + args.putInt(CALL_METHOD_USER_KEY, userId); + } + Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args); + if (b != null) { + String value = b.getPairValue(); + // Don't update our cache for reads of other users' data + if (isSelf) { + synchronized (this) { + mValues.put(name, value); + } + } else { + if (LOCAL_LOGV) Log.i(TAG, "call-query of user " + userId + + " by " + UserHandle.myUserId() + + " so not updating cache"); + } + return value; + } + // If the response Bundle is null, we fall through + // to the query interface below. + } catch (RemoteException e) { + // Not supported by the remote side? Fall through + // to query(). + } + } + + Cursor c = null; + try { + c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER, + new String[]{name}, null, null); + if (c == null) { + Log.w(TAG, "Can't get key " + name + " from " + mUri); + return null; + } + + String value = c.moveToNext() ? c.getString(0) : null; + synchronized (this) { + mValues.put(name, value); + } + if (LOCAL_LOGV) { + Log.v(TAG, "cache miss [" + mUri.getLastPathSegment() + "]: " + + name + " = " + (value == null ? "(null)" : value)); + } + return value; + } catch (RemoteException e) { + Log.w(TAG, "Can't get key " + name + " from " + mUri, e); + return null; // Return null, but don't cache it. + } finally { + if (c != null) c.close(); + } + } + } + + // region Validators + + /** @hide */ + public static interface Validator { + public boolean validate(String value); + } + + private static final Validator sBooleanValidator = + new DiscreteValueValidator(new String[] {"0", "1"}); + + private static final Validator sNonNegativeIntegerValidator = new Validator() { + @Override + public boolean validate(String value) { + try { + return Integer.parseInt(value) >= 0; + } catch (NumberFormatException e) { + return false; + } + } + }; + + private static final Validator sUriValidator = new Validator() { + @Override + public boolean validate(String value) { + try { + Uri.decode(value); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + }; + + private static final Validator sColorValidator = + new InclusiveIntegerRangeValidator(Integer.MIN_VALUE, Integer.MAX_VALUE); + + private static final Validator sAlwaysTrueValidator = new Validator() { + @Override + public boolean validate(String value) { + return true; + } + }; + + private static final class DiscreteValueValidator implements Validator { + private final String[] mValues; + + public DiscreteValueValidator(String[] values) { + mValues = values; + } + + @Override + public boolean validate(String value) { + return ArrayUtils.contains(mValues, value); + } + } + + private static final class InclusiveIntegerRangeValidator implements Validator { + private final int mMin; + private final int mMax; + + public InclusiveIntegerRangeValidator(int min, int max) { + mMin = min; + mMax = max; + } + + @Override + public boolean validate(String value) { + try { + final int intValue = Integer.parseInt(value); + return intValue >= mMin && intValue <= mMax; + } catch (NumberFormatException e) { + return false; + } + } + } + + private static final class InclusiveFloatRangeValidator implements Validator { + private final float mMin; + private final float mMax; + + public InclusiveFloatRangeValidator(float min, float max) { + mMin = min; + mMax = max; + } + + @Override + public boolean validate(String value) { + try { + final float floatValue = Float.parseFloat(value); + return floatValue >= mMin && floatValue <= mMax; + } catch (NumberFormatException e) { + return false; + } + } + } + + private static final class DelimitedListValidator implements Validator { + private final ArraySet<String> mValidValueSet; + private final String mDelimiter; + private final boolean mAllowEmptyList; + + public DelimitedListValidator(String[] validValues, String delimiter, + boolean allowEmptyList) { + mValidValueSet = new ArraySet<String>(Arrays.asList(validValues)); + mDelimiter = delimiter; + mAllowEmptyList = allowEmptyList; + } + + @Override + public boolean validate(String value) { + ArraySet<String> values = new ArraySet<String>(); + if (!TextUtils.isEmpty(value)) { + final String[] array = TextUtils.split(value, Pattern.quote(mDelimiter)); + for (String item : array) { + if (TextUtils.isEmpty(item)) { + continue; + } + values.add(item); + } + } + if (values.size() > 0) { + values.removeAll(mValidValueSet); + // values.size() will be non-zero if it contains any values not in + // mValidValueSet + return values.size() == 0; + } else if (mAllowEmptyList) { + return true; + } + + return false; + } + } + // endregion Validators + + /** + * System settings, containing miscellaneous CM system preferences. This table holds simple + * name/value pairs. There are convenience functions for accessing individual settings entries. + */ + public static final class System extends Settings.NameValueTable { + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/system"); + + public static final String SYS_PROP_CM_SETTING_VERSION = "sys.cm_settings_system_version"; + + private static final NameValueCache sNameValueCache = new NameValueCache( + SYS_PROP_CM_SETTING_VERSION, + CONTENT_URI, + CALL_METHOD_GET_SYSTEM, + CALL_METHOD_PUT_SYSTEM); + + // region Methods + + /** + * Put a delimited list as a string + * @param resolver to access the database with + * @param name to store + * @param delimiter to split + * @param list to join and store + * @hide + */ + public static void putListAsDelimitedString(ContentResolver resolver, String name, + String delimiter, List<String> list) { + String store = TextUtils.join(delimiter, list); + putString(resolver, name, store); + } + + /** + * Get a delimited string returned as a list + * @param resolver to access the database with + * @param name to store + * @param delimiter to split the list with + * @return list of strings for a specific Settings.Secure item + * @hide + */ + public static List<String> getDelimitedStringAsList(ContentResolver resolver, String name, + String delimiter) { + String baseString = getString(resolver, name); + List<String> list = new ArrayList<String>(); + if (!TextUtils.isEmpty(baseString)) { + final String[] array = TextUtils.split(baseString, Pattern.quote(delimiter)); + for (String item : array) { + if (TextUtils.isEmpty(item)) { + continue; + } + list.add(item); + } + } + return list; + } + + /** + * Construct the content URI for a particular name/value pair, useful for monitoring changes + * with a ContentObserver. + * @param name to look up in the table + * @return the corresponding content URI + */ + public static Uri getUriFor(String name) { + return Settings.NameValueTable.getUriFor(CONTENT_URI, name); + } + + /** + * Look up a name in the database. + * @param resolver to access the database with + * @param name to look up in the table + * @return the corresponding value, or null if not present + */ + public static String getString(ContentResolver resolver, String name) { + return getStringForUser(resolver, name, UserHandle.myUserId()); + } + + /** @hide */ + public static String getStringForUser(ContentResolver resolver, String name, + int userId) { + return sNameValueCache.getStringForUser(resolver, name, userId); + } + + /** + * Store a name/value pair into the database. + * @param resolver to access the database with + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + */ + public static boolean putString(ContentResolver resolver, String name, String value) { + return putStringForUser(resolver, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putStringForUser(ContentResolver resolver, String name, String value, + int userId) { + return sNameValueCache.putStringForUser(resolver, name, value, userId); + } + + /** + * Convenience function for retrieving a single settings value + * as an integer. Note that internally setting values are always + * stored as strings; this function converts the string to an integer + * for you. The default value will be returned if the setting is + * not defined or not an integer. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid integer. + */ + public static int getInt(ContentResolver cr, String name, int def) { + return getIntForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static int getIntForUser(ContentResolver cr, String name, int def, int userId) { + String v = getStringForUser(cr, name, userId); + try { + return v != null ? Integer.parseInt(v) : def; + } catch (NumberFormatException e) { + return def; + } + } + + /** + * Convenience function for retrieving a single settings value + * as an integer. Note that internally setting values are always + * stored as strings; this function converts the string to an integer + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link CMSettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * + * @throws CMSettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not an integer. + * + * @return The setting's current value. + */ + public static int getInt(ContentResolver cr, String name) + throws CMSettingNotFoundException { + return getIntForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static int getIntForUser(ContentResolver cr, String name, int userId) + throws CMSettingNotFoundException { + String v = getStringForUser(cr, name, userId); + try { + return Integer.parseInt(v); + } catch (NumberFormatException e) { + throw new CMSettingNotFoundException(name); + } + } + + /** + * Convenience function for updating a single settings value as an + * integer. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + public static boolean putInt(ContentResolver cr, String name, int value) { + return putIntForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putIntForUser(ContentResolver cr, String name, int value, + int userId) { + return putStringForUser(cr, name, Integer.toString(value), userId); + } + + /** + * Convenience function for retrieving a single settings value + * as a {@code long}. Note that internally setting values are always + * stored as strings; this function converts the string to a {@code long} + * for you. The default value will be returned if the setting is + * not defined or not a {@code long}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid {@code long}. + */ + public static long getLong(ContentResolver cr, String name, long def) { + return getLongForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static long getLongForUser(ContentResolver cr, String name, long def, + int userId) { + String valString = getStringForUser(cr, name, userId); + long value; + try { + value = valString != null ? Long.parseLong(valString) : def; + } catch (NumberFormatException e) { + value = def; + } + return value; + } + + /** + * Convenience function for retrieving a single settings value + * as a {@code long}. Note that internally setting values are always + * stored as strings; this function converts the string to a {@code long} + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link CMSettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * + * @return The setting's current value. + * @throws CMSettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not an integer. + */ + public static long getLong(ContentResolver cr, String name) + throws CMSettingNotFoundException { + return getLongForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static long getLongForUser(ContentResolver cr, String name, int userId) + throws CMSettingNotFoundException { + String valString = getStringForUser(cr, name, userId); + try { + return Long.parseLong(valString); + } catch (NumberFormatException e) { + throw new CMSettingNotFoundException(name); + } + } + + /** + * Convenience function for updating a single settings value as a long + * integer. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + public static boolean putLong(ContentResolver cr, String name, long value) { + return putLongForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putLongForUser(ContentResolver cr, String name, long value, + int userId) { + return putStringForUser(cr, name, Long.toString(value), userId); + } + + /** + * Convenience function for retrieving a single settings value + * as a floating point number. Note that internally setting values are + * always stored as strings; this function converts the string to an + * float for you. The default value will be returned if the setting + * is not defined or not a valid float. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid float. + */ + public static float getFloat(ContentResolver cr, String name, float def) { + return getFloatForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static float getFloatForUser(ContentResolver cr, String name, float def, + int userId) { + String v = getStringForUser(cr, name, userId); + try { + return v != null ? Float.parseFloat(v) : def; + } catch (NumberFormatException e) { + return def; + } + } + + /** + * Convenience function for retrieving a single system settings value + * as a float. Note that internally setting values are always + * stored as strings; this function converts the string to a float + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link CMSettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * + * @throws CMSettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not a float. + * + * @return The setting's current value. + */ + public static float getFloat(ContentResolver cr, String name) + throws CMSettingNotFoundException { + return getFloatForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static float getFloatForUser(ContentResolver cr, String name, int userId) + throws CMSettingNotFoundException { + String v = getStringForUser(cr, name, userId); + if (v == null) { + throw new CMSettingNotFoundException(name); + } + try { + return Float.parseFloat(v); + } catch (NumberFormatException e) { + throw new CMSettingNotFoundException(name); + } + } + + /** + * Convenience function for updating a single settings value as a + * floating point number. This will either create a new entry in the + * table if the given name does not exist, or modify the value of the + * existing row with that name. Note that internally setting values + * are always stored as strings, so this function converts the given + * value to a string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + public static boolean putFloat(ContentResolver cr, String name, float value) { + return putFloatForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putFloatForUser(ContentResolver cr, String name, float value, + int userId) { + return putStringForUser(cr, name, Float.toString(value), userId); + } + + // endregion + + // region System Settings + + /** + * Whether to attach a queue to media notifications. + * 0 = 0ff, 1 = on + */ + public static final String NOTIFICATION_PLAY_QUEUE = "notification_play_queue"; + + /** @hide */ + public static final Validator NOTIFICATION_PLAY_QUEUE_VALIDATOR = sBooleanValidator; + + /** + * Whether the HighTouchSensitivity is activated or not. + * 0 = off, 1 = on + */ + public static final String HIGH_TOUCH_SENSITIVITY_ENABLE = + "high_touch_sensitivity_enable"; + + /** @hide */ + public static final Validator HIGH_TOUCH_SENSITIVITY_ENABLE_VALIDATOR = + sBooleanValidator; + + /** + * Whether to enable system profiles feature + * 0 = off, 1 = on + */ + public static final String SYSTEM_PROFILES_ENABLED = "system_profiles_enabled"; + + /** @hide */ + public static final Validator SYSTEM_PROFILES_ENABLED_VALIDATOR = + sBooleanValidator; + + /** + * Whether to hide the clock, show it in the right or left + * position or show it in the center + * 0: don't show the clock + * 1: show the clock in the right position (LTR) + * 2: show the clock in the center + * 3: show the clock in the left position (LTR) + * default: 1 + */ + public static final String STATUS_BAR_CLOCK = "status_bar_clock"; + + /** @hide */ + public static final Validator STATUS_BAR_CLOCK_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 3); + + /** + * Whether the notification light will be allowed when in zen mode during downtime + */ + public static final String ZEN_ALLOW_LIGHTS = "allow_lights"; + + /** @hide */ + public static final Validator ZEN_ALLOW_LIGHTS_VALIDATOR = sBooleanValidator; + + /** + * Whether the notification light will be allowed when in zen priority mode during downtime + */ + public static final String ZEN_PRIORITY_ALLOW_LIGHTS = "zen_priority_allow_lights"; + + /** @hide */ + public static final Validator ZEN_PRIORITY_ALLOW_LIGHTS_VALIDATOR = sBooleanValidator; + + /** + * Display style of AM/PM next to clock in status bar + * 0: Normal display (Eclair stock) + * 1: Small display (Froyo stock) + * 2: No display (Gingerbread/ICS stock) + * default: 2 + */ + public static final String STATUS_BAR_AM_PM = "status_bar_am_pm"; + + /** @hide */ + public static final Validator STATUS_BAR_AM_PM_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 2); + + /** + * Display style of the status bar battery information + * 0: Display the battery an icon in portrait mode + * 2: Display the battery as a circle + * 4: Hide the battery status information + * 5: Display the battery an icon in landscape mode + * 6: Display the battery as plain text + * default: 0 + */ + public static final String STATUS_BAR_BATTERY_STYLE = "status_bar_battery_style"; + + /** @hide */ + public static final Validator STATUS_BAR_BATTERY_STYLE_VALIDATOR = + new DiscreteValueValidator(new String[] {"0", "2", "4", "5", "6"}); + + /** + * Status bar battery % + * 0: Hide the battery percentage + * 1: Display the battery percentage inside the icon + * 2: Display the battery percentage next to the icon + */ + public static final String STATUS_BAR_SHOW_BATTERY_PERCENT = + "status_bar_show_battery_percent"; + + /** @hide */ + public static final Validator STATUS_BAR_SHOW_BATTERY_PERCENT_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 2); + + /** + * Whether the phone ringtone should be played in an increasing manner + * 0 = 0ff, 1 = on + */ + public static final String INCREASING_RING = "increasing_ring"; + + /** @hide */ + public static final Validator INCREASING_RING_VALIDATOR = sBooleanValidator; + + /** + * Start volume fraction for increasing ring volume + */ + public static final String INCREASING_RING_START_VOLUME = "increasing_ring_start_vol"; + + /** @hide */ + public static final Validator INCREASING_RING_START_VOLUME_VALIDATOR = + new InclusiveFloatRangeValidator(0, 1); + + /** + * Ramp up time (seconds) for increasing ring + */ + public static final String INCREASING_RING_RAMP_UP_TIME = "increasing_ring_ramp_up_time"; + + /** @hide */ + public static final Validator INCREASING_RING_RAMP_UP_TIME_VALIDATOR = + new InclusiveIntegerRangeValidator(5, 60); + + /** + * Volume Adjust Sounds Enable, This is the noise made when using volume hard buttons + * Defaults to 1 - sounds enabled + */ + public static final String VOLUME_ADJUST_SOUNDS_ENABLED = "volume_adjust_sounds_enabled"; + + /** @hide */ + public static final Validator VOLUME_ADJUST_SOUNDS_ENABLED_VALIDATOR = + sBooleanValidator; + + /** + * Navigation controls to Use + */ + public static final String NAV_BUTTONS = "nav_buttons"; + + /** @hide */ + public static final Validator NAV_BUTTONS_VALIDATOR = + new DelimitedListValidator(new String[] {"empty", "home", "back", "search", + "recent", "menu0", "menu1", "menu2", "dpad_left", "dpad_right"}, "|", true); + + /** + * Volume key controls ringtone or media sound stream + */ + public static final String VOLUME_KEYS_CONTROL_RING_STREAM = + "volume_keys_control_ring_stream"; + + /** @hide */ + public static final Validator VOLUME_KEYS_CONTROL_RING_STREAM_VALIDATOR = + sBooleanValidator; + + /** + * boolean value. toggles using arrow key locations on nav bar + * as left and right dpad keys + */ + public static final String NAVIGATION_BAR_MENU_ARROW_KEYS = "navigation_bar_menu_arrow_keys"; + + /** @hide */ + public static final Validator NAVIGATION_BAR_MENU_ARROW_KEYS_VALIDATOR = + sBooleanValidator; + + /** + * Action to perform when the home key is long-pressed. + * (Default can be configured via config_longPressOnHomeBehavior) + * 0 - Nothing + * 1 - Menu + * 2 - App-switch + * 3 - Search + * 4 - Voice search + * 5 - In-app search + * 6 - Launch Camera + * 7 - Action Sleep + * 8 - Last app + */ + public static final String KEY_HOME_LONG_PRESS_ACTION = "key_home_long_press_action"; + + /** @hide */ + public static final Validator KEY_HOME_LONG_PRESS_ACTION_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 8); + + /** + * Action to perform when the home key is double-tapped. + * (Default can be configured via config_doubleTapOnHomeBehavior) + * (See KEY_HOME_LONG_PRESS_ACTION for valid values) + */ + public static final String KEY_HOME_DOUBLE_TAP_ACTION = "key_home_double_tap_action"; + + /** @hide */ + public static final Validator KEY_HOME_DOUBLE_TAP_ACTION_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 8); + + /** + * Whether to wake the screen with the back key, the value is boolean. + * 0 = 0ff, 1 = on + */ + public static final String BACK_WAKE_SCREEN = "back_wake_screen"; + + /** @hide */ + public static final Validator BACK_WAKE_SCREEN_VALIDATOR = + sBooleanValidator; + + /** + * Whether to wake the screen with the menu key, the value is boolean. + * 0 = 0ff, 1 = on + */ + public static final String MENU_WAKE_SCREEN = "menu_wake_screen"; + + /** @hide */ + public static final Validator MENU_WAKE_SCREENN_VALIDATOR = + sBooleanValidator; + + /** + * Whether to wake the screen with the volume keys, the value is boolean. + * 0 = 0ff, 1 = on + */ + public static final String VOLUME_WAKE_SCREEN = "volume_wake_screen"; + + /** @hide */ + public static final Validator VOLUME_WAKE_SCREEN_VALIDATOR = + sBooleanValidator; + + /** + * Action to perform when the menu key is pressed. (Default is 1) + * (See KEY_HOME_LONG_PRESS_ACTION for valid values) + */ + public static final String KEY_MENU_ACTION = "key_menu_action"; + + /** @hide */ + public static final Validator KEY_MENU_ACTION_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 8); + + /** + * Action to perform when the menu key is long-pressed. + * (Default is 0 on devices with a search key, 3 on devices without) + * (See KEY_HOME_LONG_PRESS_ACTION for valid values) + */ + public static final String KEY_MENU_LONG_PRESS_ACTION = "key_menu_long_press_action"; + + /** @hide */ + public static final Validator KEY_MENU_LONG_PRESS_ACTION_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 8); + + /** + * Action to perform when the assistant (search) key is pressed. (Default is 3) + * (See KEY_HOME_LONG_PRESS_ACTION for valid values) + */ + public static final String KEY_ASSIST_ACTION = "key_assist_action"; + + /** @hide */ + public static final Validator KEY_ASSIST_ACTION_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 8); + + /** + * Action to perform when the assistant (search) key is long-pressed. (Default is 4) + * (See KEY_HOME_LONG_PRESS_ACTION for valid values) + */ + public static final String KEY_ASSIST_LONG_PRESS_ACTION = "key_assist_long_press_action"; + + /** @hide */ + public static final Validator KEY_ASSIST_LONG_PRESS_ACTION_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 8); + + /** + * Action to perform when the app switch key is pressed. (Default is 2) + * (See KEY_HOME_LONG_PRESS_ACTION for valid values) + */ + public static final String KEY_APP_SWITCH_ACTION = "key_app_switch_action"; + + /** @hide */ + public static final Validator KEY_APP_SWITCH_ACTION_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 8); + + /** + * Action to perform when the app switch key is long-pressed. (Default is 0) + * (See KEY_HOME_LONG_PRESS_ACTION for valid values) + */ + public static final String KEY_APP_SWITCH_LONG_PRESS_ACTION = "key_app_switch_long_press_action"; + + /** @hide */ + public static final Validator KEY_APP_SWITCH_LONG_PRESS_ACTION_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 8); + + /** + * Whether to wake the screen with the home key, the value is boolean. + * 0 = 0ff, 1 = on + */ + public static final String HOME_WAKE_SCREEN = "home_wake_screen"; + + /** @hide */ + public static final Validator HOME_WAKE_SCREEN_VALIDATOR = + sBooleanValidator; + + /** + * Whether to wake the screen with the assist key, the value is boolean. + * 0 = 0ff, 1 = on + */ + public static final String ASSIST_WAKE_SCREEN = "assist_wake_screen"; + + /** @hide */ + public static final Validator ASSIST_WAKE_SCREEN_VALIDATOR = + sBooleanValidator; + + /** + * Whether to wake the screen with the app switch key, the value is boolean. + * 0 = 0ff, 1 = on + */ + public static final String APP_SWITCH_WAKE_SCREEN = "app_switch_wake_screen"; + + /** @hide */ + public static final Validator APP_SWITCH_WAKE_SCREEN_VALIDATOR = + sBooleanValidator; + + /** + * Whether to wake the screen with the camera key half-press. + * 0 = 0ff, 1 = on + */ + public static final String CAMERA_WAKE_SCREEN = "camera_wake_screen"; + + /** @hide */ + public static final Validator CAMERA_WAKE_SCREEN_VALIDATOR = + sBooleanValidator; + + /** + * Whether or not to send device back to sleep if Camera button is released ("Peek") + * 0 = 0ff, 1 = on + */ + public static final String CAMERA_SLEEP_ON_RELEASE = "camera_sleep_on_release"; + + /** @hide */ + public static final Validator CAMERA_SLEEP_ON_RELEASE_VALIDATOR = + sBooleanValidator; + + /** + * Whether to launch secure camera app when key is longpressed + * 0 = 0ff, 1 = on + */ + public static final String CAMERA_LAUNCH = "camera_launch"; + + /** @hide */ + public static final Validator CAMERA_LAUNCH_VALIDATOR = + sBooleanValidator; + + /** + * Swap volume buttons when the screen is rotated + * 0 - Disabled + * 1 - Enabled (screen is rotated by 90 or 180 degrees: phone, hybrid) + * 2 - Enabled (screen is rotated by 180 or 270 degrees: tablet) + */ + public static final String SWAP_VOLUME_KEYS_ON_ROTATION = "swap_volume_keys_on_rotation"; + + /** @hide */ + public static final Validator SWAP_VOLUME_KEYS_ON_ROTATION_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 2); + + /** + * Whether the battery light should be enabled (if hardware supports it) + * The value is boolean (1 or 0). + */ + public static final String BATTERY_LIGHT_ENABLED = "battery_light_enabled"; + + /** @hide */ + public static final Validator BATTERY_LIGHT_ENABLED_VALIDATOR = + sBooleanValidator; + + /** + * Whether the battery LED should repeatedly flash when the battery is low + * on charge. The value is boolean (1 or 0). + */ + public static final String BATTERY_LIGHT_PULSE = "battery_light_pulse"; + + /** @hide */ + public static final Validator BATTERY_LIGHT_PULSE_VALIDATOR = + sBooleanValidator; + + /** + * What color to use for the battery LED while charging - low + */ + public static final String BATTERY_LIGHT_LOW_COLOR = "battery_light_low_color"; + + /** @hide */ + public static final Validator BATTERY_LIGHT_LOW_COLOR_VALIDATOR = + sColorValidator; + + /** + * What color to use for the battery LED while charging - medium + */ + public static final String BATTERY_LIGHT_MEDIUM_COLOR = "battery_light_medium_color"; + + /** @hide */ + public static final Validator BATTERY_LIGHT_MEDIUM_COLOR_VALIDATOR = + sColorValidator; + + /** + * What color to use for the battery LED while charging - full + */ + public static final String BATTERY_LIGHT_FULL_COLOR = "battery_light_full_color"; + + /** @hide */ + public static final Validator BATTERY_LIGHT_FULL_COLOR_VALIDATOR = + sColorValidator; + + /** + * Sprint MWI Quirk: Show message wait indicator notifications + * @hide + */ + public static final String ENABLE_MWI_NOTIFICATION = "enable_mwi_notification"; + + /** @hide */ + public static final Validator ENABLE_MWI_NOTIFICATION_VALIDATOR = + sBooleanValidator; + + /** + * Check the proximity sensor during wakeup + * 0 = 0ff, 1 = on + */ + public static final String PROXIMITY_ON_WAKE = "proximity_on_wake"; + + /** @hide */ + public static final Validator PROXIMITY_ON_WAKE_VALIDATOR = + sBooleanValidator; + + /** + * Enable looking up of phone numbers of nearby places + * 0 = 0ff, 1 = on + */ + public static final String ENABLE_FORWARD_LOOKUP = "enable_forward_lookup"; + + /** @hide */ + public static final Validator ENABLE_FORWARD_LOOKUP_VALIDATOR = + sBooleanValidator; + + /** + * Enable looking up of phone numbers of people + * 0 = 0ff, 1 = on + */ + public static final String ENABLE_PEOPLE_LOOKUP = "enable_people_lookup"; + + /** @hide */ + public static final Validator ENABLE_PEOPLE_LOOKUP_VALIDATOR = + sBooleanValidator; + + /** + * Enable looking up of information of phone numbers not in the contacts + * 0 = 0ff, 1 = on + */ + public static final String ENABLE_REVERSE_LOOKUP = "enable_reverse_lookup"; + + /** @hide */ + public static final Validator ENABLE_REVERSE_LOOKUP_VALIDATOR = + sBooleanValidator; + + /** + * The forward lookup provider to be utilized by the Dialer + */ + public static final String FORWARD_LOOKUP_PROVIDER = "forward_lookup_provider"; + + /** @hide */ + public static final Validator FORWARD_LOOKUP_PROVIDER_VALIDATOR = sAlwaysTrueValidator; + + /** + * The people lookup provider to be utilized by the Dialer + */ + public static final String PEOPLE_LOOKUP_PROVIDER = "people_lookup_provider"; + + /** @hide */ + public static final Validator PEOPLE_LOOKUP_PROVIDER_VALIDATOR = sAlwaysTrueValidator; + + /** + * The reverse lookup provider to be utilized by the Dialer + */ + public static final String REVERSE_LOOKUP_PROVIDER = "reverse_lookup_provider"; + + /** @hide */ + public static final Validator REVERSE_LOOKUP_PROVIDER_VALIDATOR = sAlwaysTrueValidator; + + /** + * The OpenCNAM paid account ID to be utilized by the Dialer + */ + public static final String DIALER_OPENCNAM_ACCOUNT_SID = "dialer_opencnam_account_sid"; + + /** @hide */ + public static final Validator DIALER_OPENCNAM_ACCOUNT_SID_VALIDATOR = + sAlwaysTrueValidator; + + /** + * The OpenCNAM authentication token to be utilized by the Dialer + */ + public static final String DIALER_OPENCNAM_AUTH_TOKEN = "dialer_opencnam_auth_token"; + + /** @hide */ + public static final Validator DIALER_OPENCNAM_AUTH_TOKEN_VALIDATOR = + sAlwaysTrueValidator; + + /** + * Color temperature of the display during the day + */ + public static final String DISPLAY_TEMPERATURE_DAY = "display_temperature_day"; + + /** @hide */ + public static final Validator DISPLAY_TEMPERATURE_DAY_VALIDATOR = + new InclusiveIntegerRangeValidator(1000, 10000); + + /** + * Color temperature of the display at night + */ + public static final String DISPLAY_TEMPERATURE_NIGHT = "display_temperature_night"; + + /** @hide */ + public static final Validator DISPLAY_TEMPERATURE_NIGHT_VALIDATOR = + new InclusiveIntegerRangeValidator(1000, 10000); + + /** + * Display color temperature adjustment mode, one of DAY (default), NIGHT, or AUTO. + */ + public static final String DISPLAY_TEMPERATURE_MODE = "display_temperature_mode"; + + /** @hide */ + public static final Validator DISPLAY_TEMPERATURE_MODE_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 4); + + /** + * Automatic outdoor mode + * 0 = 0ff, 1 = on + */ + public static final String DISPLAY_AUTO_OUTDOOR_MODE = "display_auto_outdoor_mode"; + + /** @hide */ + public static final Validator DISPLAY_AUTO_OUTDOOR_MODE_VALIDATOR = + sBooleanValidator; + + /** + * Use display power saving features such as CABC or CABL + * 0 = 0ff, 1 = on + */ + public static final String DISPLAY_LOW_POWER = "display_low_power"; + + /** @hide */ + public static final Validator DISPLAY_LOW_POWER_VALIDATOR = + sBooleanValidator; + + /** + * Use color enhancement feature of display + * 0 = 0ff, 1 = on + */ + public static final String DISPLAY_COLOR_ENHANCE = "display_color_enhance"; + + /** @hide */ + public static final Validator DISPLAY_COLOR_ENHANCE_VALIDATOR = + sBooleanValidator; + + /** + * Manual display color adjustments (RGB values as floats, separated by spaces) + */ + public static final String DISPLAY_COLOR_ADJUSTMENT = "display_color_adjustment"; + + /** @hide */ + public static final Validator DISPLAY_COLOR_ADJUSTMENT_VALIDATOR = + new Validator() { + @Override + public boolean validate(String value) { + String[] colorAdjustment = value == null ? + null : value.split(" "); + if (colorAdjustment != null && colorAdjustment.length != 3) { + return false; + } + Validator floatValidator = new InclusiveFloatRangeValidator(0, 1); + return colorAdjustment == null || + floatValidator.validate(colorAdjustment[0]) && + floatValidator.validate(colorAdjustment[1]) && + floatValidator.validate(colorAdjustment[2]); + } + }; + + /** + * Did we tell about how they can stop breaking their eyes? + * @hide + */ + public static final String LIVE_DISPLAY_HINTED = "live_display_hinted"; + + /** @hide */ + public static final Validator LIVE_DISPLAY_HINTED_VALIDATOR = + sBooleanValidator; + + /** + * Enable statusbar double tap gesture on to put device to sleep + * 0 = 0ff, 1 = on + */ + public static final String DOUBLE_TAP_SLEEP_GESTURE = "double_tap_sleep_gesture"; + + /** @hide */ + public static final Validator DOUBLE_TAP_SLEEP_GESTURE_VALIDATOR = + sBooleanValidator; + + /** + * Boolean value on whether to show weather in the statusbar + * 0 = 0ff, 1 = on + */ + public static final String STATUS_BAR_SHOW_WEATHER = "status_bar_show_weather"; + + /** @hide */ + public static final Validator STATUS_BAR_SHOW_WEATHER_VALIDATOR = + sBooleanValidator; + + /** + * Show search bar in recents + * 0 = 0ff, 1 = on + */ + public static final String RECENTS_SHOW_SEARCH_BAR = "recents_show_search_bar"; + + /** @hide */ + public static final Validator RECENTS_SHOW_SEARCH_BAR_VALIDATOR = + sBooleanValidator; + + /** + * Whether navigation bar is placed on the left side in landscape mode + * 0 = 0ff, 1 = on + */ + public static final String NAVBAR_LEFT_IN_LANDSCAPE = "navigation_bar_left"; + + /** @hide */ + public static final Validator NAVBAR_LEFT_IN_LANDSCAPE_VALIDATOR = + sBooleanValidator; + + /** + * Locale for secondary overlay on dialer for t9 search input + */ + public static final String T9_SEARCH_INPUT_LOCALE = "t9_search_input_locale"; + + /** @hide */ + public static final Validator T9_SEARCH_INPUT_LOCALE_VALIDATOR = + new Validator() { + @Override + public boolean validate(String value) { + final Locale locale = new Locale(value); + return ArrayUtils.contains(Locale.getAvailableLocales(), locale); + } + }; + + /** + * If all file types can be accepted over Bluetooth OBEX. + * 0 = 0ff, 1 = on + */ + public static final String BLUETOOTH_ACCEPT_ALL_FILES = + "bluetooth_accept_all_files"; + + /** @hide */ + public static final Validator BLUETOOTH_ACCEPT_ALL_FILES_VALIDATOR = + sBooleanValidator; + + /** + * Whether to scramble a pin unlock layout + * 0 = 0ff, 1 = on + */ + public static final String LOCKSCREEN_PIN_SCRAMBLE_LAYOUT = + "lockscreen_scramble_pin_layout"; + + /** @hide */ + public static final Validator LOCKSCREEN_PIN_SCRAMBLE_LAYOUT_VALIDATOR = + sBooleanValidator; + + /** + * Whether to show the alarm clock icon in the status bar. + * 0 = 0ff, 1 = on + */ + public static final String SHOW_ALARM_ICON = "show_alarm_icon"; + + /** @hide */ + public static final Validator SHOW_ALARM_ICON_VALIDATOR = + sBooleanValidator; + + /** + * Whether to show the IME switcher in the status bar + * 0 = 0ff, 1 = on + */ + public static final String STATUS_BAR_IME_SWITCHER = "status_bar_ime_switcher"; + + /** @hide */ + public static final Validator STATUS_BAR_IME_SWITCHER_VALIDATOR = + sBooleanValidator; + + /** + * Whether to allow one finger quick settings expansion on the side of the statusbar. + * 0 = 0ff, 1 = right, 2 = left + */ + public static final String STATUS_BAR_QUICK_QS_PULLDOWN = "qs_quick_pulldown"; + + /** @hide */ + public static final Validator STATUS_BAR_QUICK_QS_PULLDOWN_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 2); + + /** + * Whether to show the brightness slider in quick settings panel. + * 0 = 0ff, 1 = on + */ + public static final String QS_SHOW_BRIGHTNESS_SLIDER = "qs_show_brightness_slider"; + + /** @hide */ + public static final Validator QS_SHOW_BRIGHTNESS_SLIDER_VALIDATOR = + sBooleanValidator; + + /** + * Whether to control brightness from status bar + * 0 = 0ff, 1 = on + */ + public static final String STATUS_BAR_BRIGHTNESS_CONTROL = "status_bar_brightness_control"; + + /** @hide */ + public static final Validator STATUS_BAR_BRIGHTNESS_CONTROL_VALIDATOR = + sBooleanValidator; + + /** + * Whether or not volume button music controls should be enabled to seek media tracks + * 0 = 0ff, 1 = on + */ + public static final String VOLBTN_MUSIC_CONTROLS = "volbtn_music_controls"; + + /** @hide */ + public static final Validator VOLBTN_MUSIC_CONTROLS_VALIDATOR = + sBooleanValidator; + + /** + * Use EdgeGesture Service for system gestures in PhoneWindowManager + * 0 = 0ff, 1 = on + */ + public static final String USE_EDGE_SERVICE_FOR_GESTURES = "edge_service_for_gestures"; + + /** @hide */ + public static final Validator USE_EDGE_SERVICE_FOR_GESTURES_VALIDATOR = + sBooleanValidator; + + /** + * Show the pending notification counts as overlays on the status bar + */ + public static final String STATUS_BAR_NOTIF_COUNT = "status_bar_notif_count"; + + /** @hide */ + public static final Validator STATUS_BAR_NOTIF_COUNT_VALIDATOR = + sBooleanValidator; + + /** + * Call recording format value + * 0: AMR_WB + * 1: MPEG_4 + * Default: 0 + */ + public static final String CALL_RECORDING_FORMAT = "call_recording_format"; + + /** @hide */ + public static final Validator CALL_RECORDING_FORMAT_VALIDATOR = + new InclusiveIntegerRangeValidator(0, 1); + + /** + * Contains the notifications light maximum brightness to use. + * Values range from 1 to 255 + */ + public static final String NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL = + "notification_light_brightness_level"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_VALIDATOR = + new InclusiveIntegerRangeValidator(1, 255); + + /** + * Whether to use the all the LEDs for the notifications or just one. + * 0 = 0ff, 1 = on + */ + public static final String NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE = + "notification_light_multiple_leds_enable"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE_VALIDATOR = + sBooleanValidator; + + /** + * Whether to allow notifications with the screen on or DayDreams. + * The value is boolean (1 or 0). Default will always be false. + */ + public static final String NOTIFICATION_LIGHT_SCREEN_ON = + "notification_light_screen_on_enable"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_SCREEN_ON_VALIDATOR = + sBooleanValidator; + + /** + * What color to use for the notification LED by default + */ + public static final String NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR = + "notification_light_pulse_default_color"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR_VALIDATOR = + sColorValidator; + + /** + * How long to flash the notification LED by default + */ + public static final String NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON = + "notification_light_pulse_default_led_on"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON_VALIDATOR = + sNonNegativeIntegerValidator; + + /** + * How long to wait between flashes for the notification LED by default + */ + public static final String NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF = + "notification_light_pulse_default_led_off"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF_VALIDATOR = + sNonNegativeIntegerValidator; + + /** + * What color to use for the missed call notification LED + */ + public static final String NOTIFICATION_LIGHT_PULSE_CALL_COLOR = + "notification_light_pulse_call_color"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_PULSE_CALL_COLOR_VALIDATOR = + sColorValidator; + + /** + * How long to flash the missed call notification LED + */ + public static final String NOTIFICATION_LIGHT_PULSE_CALL_LED_ON = + "notification_light_pulse_call_led_on"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_PULSE_CALL_LED_ON_VALIDATOR = + sNonNegativeIntegerValidator; + + /** + * How long to wait between flashes for the missed call notification LED + */ + public static final String NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF = + "notification_light_pulse_call_led_off"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF_VALIDATOR = + sNonNegativeIntegerValidator; + + /** + * What color to use for the voicemail notification LED + */ + public static final String NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR = + "notification_light_pulse_vmail_color"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR_VALIDATOR = + sColorValidator; + + /** + * How long to flash the voicemail notification LED + */ + public static final String NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON = + "notification_light_pulse_vmail_led_on"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON_VALIDATOR = + sNonNegativeIntegerValidator; + + /** + * How long to wait between flashes for the voicemail notification LED + */ + public static final String NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF = + "notification_light_pulse_vmail_led_off"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF_VALIDATOR = + sNonNegativeIntegerValidator; + + /** + * Whether to use the custom LED values for the notification pulse LED. + * 0 = 0ff, 1 = on + */ + public static final String NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE = + "notification_light_pulse_custom_enable"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE_VALIDATOR = + sBooleanValidator; + + /** + * Which custom LED values to use for the notification pulse LED. + */ + public static final String NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES = + "notification_light_pulse_custom_values"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES_VALIDATOR = + new Validator() { + @Override + public boolean validate(String value) { + if (TextUtils.isEmpty(value)) { + return true; + } + + for (String packageValuesString : value.split("\\|")) { + String[] packageValues = packageValuesString.split("="); + if (packageValues.length != 2) { + if (LOCAL_LOGV) { + Log.d(TAG, "Incorrect number of package values: " + + packageValues.length); + } + return false; + } + String packageName = packageValues[0]; + if (TextUtils.isEmpty(packageName)) { + if (LOCAL_LOGV) Log.d(TAG, "Empty package name"); + return false; + } + String[] values = packageValues[1].split(";"); + if (values.length != 3) { + if (LOCAL_LOGV) { + Log.d(TAG, "Incorrect number of values: " + values.length); + } + return false; + } + try { + // values[0] is LED color + if (!sColorValidator.validate(values[0])) { + if (LOCAL_LOGV) { + Log.d(TAG, "Invalid LED color (" + values[0] + ") for " + + packageName); + } + return false; + } + // values[1] is the LED on time and should be non-negative + if (!sNonNegativeIntegerValidator.validate(values[1])) { + if (LOCAL_LOGV) { + Log.d(TAG, "Invalid LED on time (" + values[1] + ") for " + + packageName); + } + return false; + } + // values[1] is the LED off time and should be non-negative + if (!sNonNegativeIntegerValidator.validate(values[2])) { + if (LOCAL_LOGV) { + Log.d(TAG, "Invalid LED off time (" + values[2] + ") for " + + packageName); + } + return false; + } + } catch (NumberFormatException e) { + return false; + } + } + // if we make it all the way through then the data is considered valid + return true; + } + }; + + /** + * Whether we automatically generate notification LED colors or just + * use the boring default. + * + * @hide + */ + public static final String NOTIFICATION_LIGHT_COLOR_AUTO = + "notification_light_color_auto"; + + /** @hide */ + public static final Validator NOTIFICATION_LIGHT_COLOR_AUTO_VALIDATOR = + sBooleanValidator; + + /** + * Whether or not to launch default music player when headset is connected + */ + public static final String HEADSET_CONNECT_PLAYER = "headset_connect_player"; + + /** @hide */ + public static final Validator HEADSET_CONNECT_PLAYER_VALIDATOR = sBooleanValidator; + + /** + * Whether or not to vibrate when a touchscreen gesture is detected + */ + public static final String TOUCHSCREEN_GESTURE_HAPTIC_FEEDBACK = + "touchscreen_gesture_haptic_feedback"; + + /** @hide */ + public static final Validator TOUCHSCREEN_GESTURE_HAPTIC_FEEDBACK_VALIDATOR = + sBooleanValidator; + + /** + * I can haz more bukkits + * @hide + */ + public static final String __MAGICAL_TEST_PASSING_ENABLER = + "___magical_test_passing_enabler"; + + /** + * Don't + * @hide + * me bro + */ + public static final Validator __MAGICAL_TEST_PASSING_ENABLER_VALIDATOR = + sBooleanValidator; + + /** + * @hide + */ + public static final String[] LEGACY_SYSTEM_SETTINGS = new String[]{ + CMSettings.System.NAV_BUTTONS, + CMSettings.System.KEY_HOME_LONG_PRESS_ACTION, + CMSettings.System.KEY_HOME_DOUBLE_TAP_ACTION, + CMSettings.System.BACK_WAKE_SCREEN, + CMSettings.System.MENU_WAKE_SCREEN, + CMSettings.System.VOLUME_WAKE_SCREEN, + CMSettings.System.KEY_MENU_ACTION, + CMSettings.System.KEY_MENU_LONG_PRESS_ACTION, + CMSettings.System.KEY_ASSIST_ACTION, + CMSettings.System.KEY_ASSIST_LONG_PRESS_ACTION, + CMSettings.System.KEY_APP_SWITCH_ACTION, + CMSettings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION, + CMSettings.System.HOME_WAKE_SCREEN, + CMSettings.System.ASSIST_WAKE_SCREEN, + CMSettings.System.APP_SWITCH_WAKE_SCREEN, + CMSettings.System.CAMERA_WAKE_SCREEN, + CMSettings.System.CAMERA_SLEEP_ON_RELEASE, + CMSettings.System.CAMERA_LAUNCH, + CMSettings.System.SWAP_VOLUME_KEYS_ON_ROTATION, + CMSettings.System.BATTERY_LIGHT_ENABLED, + CMSettings.System.BATTERY_LIGHT_PULSE, + CMSettings.System.BATTERY_LIGHT_LOW_COLOR, + CMSettings.System.BATTERY_LIGHT_MEDIUM_COLOR, + CMSettings.System.BATTERY_LIGHT_FULL_COLOR, + CMSettings.System.ENABLE_MWI_NOTIFICATION, + CMSettings.System.PROXIMITY_ON_WAKE, + CMSettings.System.ENABLE_FORWARD_LOOKUP, + CMSettings.System.ENABLE_PEOPLE_LOOKUP, + CMSettings.System.ENABLE_REVERSE_LOOKUP, + CMSettings.System.FORWARD_LOOKUP_PROVIDER, + CMSettings.System.PEOPLE_LOOKUP_PROVIDER, + CMSettings.System.REVERSE_LOOKUP_PROVIDER, + CMSettings.System.DIALER_OPENCNAM_ACCOUNT_SID, + CMSettings.System.DIALER_OPENCNAM_AUTH_TOKEN, + CMSettings.System.DISPLAY_TEMPERATURE_DAY, + CMSettings.System.DISPLAY_TEMPERATURE_NIGHT, + CMSettings.System.DISPLAY_TEMPERATURE_MODE, + CMSettings.System.DISPLAY_AUTO_OUTDOOR_MODE, + CMSettings.System.DISPLAY_LOW_POWER, + CMSettings.System.DISPLAY_COLOR_ENHANCE, + CMSettings.System.DISPLAY_COLOR_ADJUSTMENT, + CMSettings.System.LIVE_DISPLAY_HINTED, + CMSettings.System.DOUBLE_TAP_SLEEP_GESTURE, + CMSettings.System.STATUS_BAR_SHOW_WEATHER, + CMSettings.System.RECENTS_SHOW_SEARCH_BAR, + CMSettings.System.NAVBAR_LEFT_IN_LANDSCAPE, + CMSettings.System.T9_SEARCH_INPUT_LOCALE, + CMSettings.System.BLUETOOTH_ACCEPT_ALL_FILES, + CMSettings.System.LOCKSCREEN_PIN_SCRAMBLE_LAYOUT, + CMSettings.System.SHOW_ALARM_ICON, + CMSettings.System.STATUS_BAR_IME_SWITCHER, + CMSettings.System.QS_SHOW_BRIGHTNESS_SLIDER, + CMSettings.System.STATUS_BAR_BRIGHTNESS_CONTROL, + CMSettings.System.VOLBTN_MUSIC_CONTROLS, + CMSettings.System.SWAP_VOLUME_KEYS_ON_ROTATION, + CMSettings.System.USE_EDGE_SERVICE_FOR_GESTURES, + CMSettings.System.STATUS_BAR_NOTIF_COUNT, + CMSettings.System.CALL_RECORDING_FORMAT, + CMSettings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL, + CMSettings.System.NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE, + CMSettings.System.NOTIFICATION_LIGHT_SCREEN_ON, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_ON, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES, + CMSettings.System.STATUS_BAR_QUICK_QS_PULLDOWN, + CMSettings.System.VOLUME_ADJUST_SOUNDS_ENABLED, + CMSettings.System.SYSTEM_PROFILES_ENABLED, + CMSettings.System.INCREASING_RING, + CMSettings.System.INCREASING_RING_START_VOLUME, + CMSettings.System.INCREASING_RING_RAMP_UP_TIME, + CMSettings.System.STATUS_BAR_CLOCK, + CMSettings.System.STATUS_BAR_AM_PM, + CMSettings.System.STATUS_BAR_BATTERY_STYLE, + CMSettings.System.STATUS_BAR_SHOW_BATTERY_PERCENT, + CMSettings.System.VOLUME_KEYS_CONTROL_RING_STREAM, + CMSettings.System.NAVIGATION_BAR_MENU_ARROW_KEYS, + CMSettings.System.HEADSET_CONNECT_PLAYER, + CMSettings.System.ZEN_ALLOW_LIGHTS, + CMSettings.System.TOUCHSCREEN_GESTURE_HAPTIC_FEEDBACK, + }; + + /** + * @hide + */ + public static boolean isLegacySetting(String key) { + return ArrayUtils.contains(LEGACY_SYSTEM_SETTINGS, key); + } + + /** + * @hide + */ + public static boolean shouldInterceptSystemProvider(String key) { + return key.equals(System.SYSTEM_PROFILES_ENABLED); + } + + /** + * Mapping of validators for all system settings. This map is used to validate both valid + * keys as well as validating the values for those keys. + * + * Note: Make sure if you add a new System setting you create a Validator for it and add + * it to this map. + * + * @hide + */ + public static final Map<String, Validator> VALIDATORS = + new ArrayMap<String, Validator>(); + static { + VALIDATORS.put(NOTIFICATION_PLAY_QUEUE, NOTIFICATION_PLAY_QUEUE_VALIDATOR); + VALIDATORS.put(HIGH_TOUCH_SENSITIVITY_ENABLE, + HIGH_TOUCH_SENSITIVITY_ENABLE_VALIDATOR); + VALIDATORS.put(SYSTEM_PROFILES_ENABLED, SYSTEM_PROFILES_ENABLED_VALIDATOR); + VALIDATORS.put(STATUS_BAR_CLOCK, STATUS_BAR_CLOCK_VALIDATOR); + VALIDATORS.put(STATUS_BAR_AM_PM, STATUS_BAR_AM_PM_VALIDATOR); + VALIDATORS.put(STATUS_BAR_BATTERY_STYLE, STATUS_BAR_BATTERY_STYLE_VALIDATOR); + VALIDATORS.put(STATUS_BAR_SHOW_BATTERY_PERCENT, + STATUS_BAR_SHOW_BATTERY_PERCENT_VALIDATOR); + VALIDATORS.put(INCREASING_RING, INCREASING_RING_VALIDATOR); + VALIDATORS.put(INCREASING_RING_START_VOLUME, + INCREASING_RING_START_VOLUME_VALIDATOR); + VALIDATORS.put(INCREASING_RING_RAMP_UP_TIME, + INCREASING_RING_RAMP_UP_TIME_VALIDATOR); + VALIDATORS.put(VOLUME_ADJUST_SOUNDS_ENABLED, + VOLUME_ADJUST_SOUNDS_ENABLED_VALIDATOR); + VALIDATORS.put(NAV_BUTTONS, NAV_BUTTONS_VALIDATOR); + VALIDATORS.put(VOLUME_KEYS_CONTROL_RING_STREAM, + VOLUME_KEYS_CONTROL_RING_STREAM_VALIDATOR); + VALIDATORS.put(NAVIGATION_BAR_MENU_ARROW_KEYS, + NAVIGATION_BAR_MENU_ARROW_KEYS_VALIDATOR); + VALIDATORS.put(KEY_HOME_LONG_PRESS_ACTION, KEY_HOME_LONG_PRESS_ACTION_VALIDATOR); + VALIDATORS.put(KEY_HOME_DOUBLE_TAP_ACTION, KEY_HOME_DOUBLE_TAP_ACTION_VALIDATOR); + VALIDATORS.put(BACK_WAKE_SCREEN, BACK_WAKE_SCREEN_VALIDATOR); + VALIDATORS.put(MENU_WAKE_SCREEN, MENU_WAKE_SCREENN_VALIDATOR); + VALIDATORS.put(VOLUME_WAKE_SCREEN, VOLUME_WAKE_SCREEN_VALIDATOR); + VALIDATORS.put(KEY_MENU_ACTION, KEY_MENU_ACTION_VALIDATOR); + VALIDATORS.put(KEY_MENU_LONG_PRESS_ACTION, KEY_MENU_LONG_PRESS_ACTION_VALIDATOR); + VALIDATORS.put(KEY_ASSIST_ACTION, KEY_ASSIST_ACTION_VALIDATOR); + VALIDATORS.put(KEY_ASSIST_LONG_PRESS_ACTION, + KEY_ASSIST_LONG_PRESS_ACTION_VALIDATOR); + VALIDATORS.put(KEY_APP_SWITCH_ACTION, KEY_APP_SWITCH_ACTION_VALIDATOR); + VALIDATORS.put(KEY_APP_SWITCH_LONG_PRESS_ACTION, + KEY_APP_SWITCH_LONG_PRESS_ACTION_VALIDATOR); + VALIDATORS.put(HOME_WAKE_SCREEN, HOME_WAKE_SCREEN_VALIDATOR); + VALIDATORS.put(ASSIST_WAKE_SCREEN, ASSIST_WAKE_SCREEN_VALIDATOR); + VALIDATORS.put(APP_SWITCH_WAKE_SCREEN, APP_SWITCH_WAKE_SCREEN_VALIDATOR); + VALIDATORS.put(CAMERA_WAKE_SCREEN, CAMERA_WAKE_SCREEN_VALIDATOR); + VALIDATORS.put(CAMERA_SLEEP_ON_RELEASE, CAMERA_SLEEP_ON_RELEASE_VALIDATOR); + VALIDATORS.put(CAMERA_LAUNCH, CAMERA_LAUNCH_VALIDATOR); + VALIDATORS.put(SWAP_VOLUME_KEYS_ON_ROTATION, + SWAP_VOLUME_KEYS_ON_ROTATION_VALIDATOR); + VALIDATORS.put(BATTERY_LIGHT_ENABLED, BATTERY_LIGHT_ENABLED_VALIDATOR); + VALIDATORS.put(BATTERY_LIGHT_PULSE, BATTERY_LIGHT_PULSE_VALIDATOR); + VALIDATORS.put(BATTERY_LIGHT_LOW_COLOR, BATTERY_LIGHT_LOW_COLOR_VALIDATOR); + VALIDATORS.put(BATTERY_LIGHT_MEDIUM_COLOR, BATTERY_LIGHT_MEDIUM_COLOR_VALIDATOR); + VALIDATORS.put(BATTERY_LIGHT_FULL_COLOR, BATTERY_LIGHT_FULL_COLOR_VALIDATOR); + VALIDATORS.put(ENABLE_MWI_NOTIFICATION, ENABLE_MWI_NOTIFICATION_VALIDATOR); + VALIDATORS.put(PROXIMITY_ON_WAKE, PROXIMITY_ON_WAKE_VALIDATOR); + VALIDATORS.put(ENABLE_FORWARD_LOOKUP, ENABLE_FORWARD_LOOKUP_VALIDATOR); + VALIDATORS.put(ENABLE_PEOPLE_LOOKUP, ENABLE_PEOPLE_LOOKUP_VALIDATOR); + VALIDATORS.put(ENABLE_REVERSE_LOOKUP, ENABLE_REVERSE_LOOKUP_VALIDATOR); + VALIDATORS.put(FORWARD_LOOKUP_PROVIDER, FORWARD_LOOKUP_PROVIDER_VALIDATOR); + VALIDATORS.put(PEOPLE_LOOKUP_PROVIDER, PEOPLE_LOOKUP_PROVIDER_VALIDATOR); + VALIDATORS.put(REVERSE_LOOKUP_PROVIDER, REVERSE_LOOKUP_PROVIDER_VALIDATOR); + VALIDATORS.put(DIALER_OPENCNAM_ACCOUNT_SID, + DIALER_OPENCNAM_ACCOUNT_SID_VALIDATOR); + VALIDATORS.put(DIALER_OPENCNAM_AUTH_TOKEN, DIALER_OPENCNAM_AUTH_TOKEN_VALIDATOR); + VALIDATORS.put(DISPLAY_TEMPERATURE_DAY, DISPLAY_TEMPERATURE_DAY_VALIDATOR); + VALIDATORS.put(DISPLAY_TEMPERATURE_NIGHT, DISPLAY_TEMPERATURE_NIGHT_VALIDATOR); + VALIDATORS.put(DISPLAY_TEMPERATURE_MODE, DISPLAY_TEMPERATURE_MODE_VALIDATOR); + VALIDATORS.put(DISPLAY_AUTO_OUTDOOR_MODE, DISPLAY_AUTO_OUTDOOR_MODE_VALIDATOR); + VALIDATORS.put(DISPLAY_LOW_POWER, DISPLAY_LOW_POWER_VALIDATOR); + VALIDATORS.put(DISPLAY_COLOR_ENHANCE, DISPLAY_COLOR_ENHANCE_VALIDATOR); + VALIDATORS.put(DISPLAY_COLOR_ADJUSTMENT, DISPLAY_COLOR_ADJUSTMENT_VALIDATOR); + VALIDATORS.put(LIVE_DISPLAY_HINTED, LIVE_DISPLAY_HINTED_VALIDATOR); + VALIDATORS.put(DOUBLE_TAP_SLEEP_GESTURE, DOUBLE_TAP_SLEEP_GESTURE_VALIDATOR); + VALIDATORS.put(STATUS_BAR_SHOW_WEATHER, STATUS_BAR_SHOW_WEATHER_VALIDATOR); + VALIDATORS.put(RECENTS_SHOW_SEARCH_BAR, RECENTS_SHOW_SEARCH_BAR_VALIDATOR); + VALIDATORS.put(NAVBAR_LEFT_IN_LANDSCAPE, NAVBAR_LEFT_IN_LANDSCAPE_VALIDATOR); + VALIDATORS.put(T9_SEARCH_INPUT_LOCALE, T9_SEARCH_INPUT_LOCALE_VALIDATOR); + VALIDATORS.put(BLUETOOTH_ACCEPT_ALL_FILES, BLUETOOTH_ACCEPT_ALL_FILES_VALIDATOR); + VALIDATORS.put(LOCKSCREEN_PIN_SCRAMBLE_LAYOUT, + LOCKSCREEN_PIN_SCRAMBLE_LAYOUT_VALIDATOR); + VALIDATORS.put(SHOW_ALARM_ICON, SHOW_ALARM_ICON_VALIDATOR); + VALIDATORS.put(STATUS_BAR_IME_SWITCHER, STATUS_BAR_IME_SWITCHER_VALIDATOR); + VALIDATORS.put(STATUS_BAR_QUICK_QS_PULLDOWN, + STATUS_BAR_QUICK_QS_PULLDOWN_VALIDATOR); + VALIDATORS.put(QS_SHOW_BRIGHTNESS_SLIDER, QS_SHOW_BRIGHTNESS_SLIDER_VALIDATOR); + VALIDATORS.put(STATUS_BAR_BRIGHTNESS_CONTROL, + STATUS_BAR_BRIGHTNESS_CONTROL_VALIDATOR); + VALIDATORS.put(VOLBTN_MUSIC_CONTROLS, VOLBTN_MUSIC_CONTROLS_VALIDATOR); + VALIDATORS.put(USE_EDGE_SERVICE_FOR_GESTURES, + USE_EDGE_SERVICE_FOR_GESTURES_VALIDATOR); + VALIDATORS.put(STATUS_BAR_NOTIF_COUNT, STATUS_BAR_NOTIF_COUNT_VALIDATOR); + VALIDATORS.put(CALL_RECORDING_FORMAT, CALL_RECORDING_FORMAT_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL, + NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE, + NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_SCREEN_ON, + NOTIFICATION_LIGHT_SCREEN_ON_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, + NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, + NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, + NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_CALL_COLOR, + NOTIFICATION_LIGHT_PULSE_CALL_COLOR_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_CALL_LED_ON, + NOTIFICATION_LIGHT_PULSE_CALL_LED_ON_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF, + NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, + NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON, + NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF, + NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE, + NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES, + NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES_VALIDATOR); + VALIDATORS.put(NOTIFICATION_LIGHT_COLOR_AUTO, + NOTIFICATION_LIGHT_COLOR_AUTO_VALIDATOR); + VALIDATORS.put(HEADSET_CONNECT_PLAYER, HEADSET_CONNECT_PLAYER_VALIDATOR); + VALIDATORS.put(ZEN_ALLOW_LIGHTS, ZEN_ALLOW_LIGHTS_VALIDATOR); + VALIDATORS.put(ZEN_PRIORITY_ALLOW_LIGHTS, ZEN_PRIORITY_ALLOW_LIGHTS_VALIDATOR); + VALIDATORS.put(TOUCHSCREEN_GESTURE_HAPTIC_FEEDBACK, + TOUCHSCREEN_GESTURE_HAPTIC_FEEDBACK_VALIDATOR); + VALIDATORS.put(__MAGICAL_TEST_PASSING_ENABLER, + __MAGICAL_TEST_PASSING_ENABLER_VALIDATOR); + }; + // endregion + } + + /** + * Secure settings, containing miscellaneous CM secure preferences. This + * table holds simple name/value pairs. There are convenience + * functions for accessing individual settings entries. + */ + public static final class Secure extends Settings.NameValueTable { + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/secure"); + + public static final String SYS_PROP_CM_SETTING_VERSION = "sys.cm_settings_secure_version"; + + private static final NameValueCache sNameValueCache = new NameValueCache( + SYS_PROP_CM_SETTING_VERSION, + CONTENT_URI, + CALL_METHOD_GET_SECURE, + CALL_METHOD_PUT_SECURE); + + // region Methods + + /** + * Put a delimited list as a string + * @param resolver to access the database with + * @param name to store + * @param delimiter to split + * @param list to join and store + * @hide + */ + public static void putListAsDelimitedString(ContentResolver resolver, String name, + String delimiter, List<String> list) { + String store = TextUtils.join(delimiter, list); + putString(resolver, name, store); + } + + /** + * Get a delimited string returned as a list + * @param resolver to access the database with + * @param name to store + * @param delimiter to split the list with + * @return list of strings for a specific Settings.Secure item + * @hide + */ + public static List<String> getDelimitedStringAsList(ContentResolver resolver, String name, + String delimiter) { + String baseString = getString(resolver, name); + List<String> list = new ArrayList<String>(); + if (!TextUtils.isEmpty(baseString)) { + final String[] array = TextUtils.split(baseString, Pattern.quote(delimiter)); + for (String item : array) { + if (TextUtils.isEmpty(item)) { + continue; + } + list.add(item); + } + } + return list; + } + + /** + * Construct the content URI for a particular name/value pair, useful for monitoring changes + * with a ContentObserver. + * @param name to look up in the table + * @return the corresponding content URI + */ + public static Uri getUriFor(String name) { + return Settings.NameValueTable.getUriFor(CONTENT_URI, name); + } + + /** + * Look up a name in the database. + * @param resolver to access the database with + * @param name to look up in the table + * @return the corresponding value, or null if not present + */ + public static String getString(ContentResolver resolver, String name) { + return getStringForUser(resolver, name, UserHandle.myUserId()); + } + + /** @hide */ + public static String getStringForUser(ContentResolver resolver, String name, + int userId) { + return sNameValueCache.getStringForUser(resolver, name, userId); + } + + /** + * Store a name/value pair into the database. + * @param resolver to access the database with + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + */ + public static boolean putString(ContentResolver resolver, String name, String value) { + return putStringForUser(resolver, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putStringForUser(ContentResolver resolver, String name, String value, + int userId) { + return sNameValueCache.putStringForUser(resolver, name, value, userId); + } + + /** + * Convenience function for retrieving a single settings value + * as an integer. Note that internally setting values are always + * stored as strings; this function converts the string to an integer + * for you. The default value will be returned if the setting is + * not defined or not an integer. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid integer. + */ + public static int getInt(ContentResolver cr, String name, int def) { + return getIntForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static int getIntForUser(ContentResolver cr, String name, int def, int userId) { + String v = getStringForUser(cr, name, userId); + try { + return v != null ? Integer.parseInt(v) : def; + } catch (NumberFormatException e) { + return def; + } + } + + /** + * Convenience function for retrieving a single settings value + * as an integer. Note that internally setting values are always + * stored as strings; this function converts the string to an integer + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link CMSettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * + * @throws CMSettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not an integer. + * + * @return The setting's current value. + */ + public static int getInt(ContentResolver cr, String name) + throws CMSettingNotFoundException { + return getIntForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static int getIntForUser(ContentResolver cr, String name, int userId) + throws CMSettingNotFoundException { + String v = getStringForUser(cr, name, userId); + try { + return Integer.parseInt(v); + } catch (NumberFormatException e) { + throw new CMSettingNotFoundException(name); + } + } + + /** + * Convenience function for updating a single settings value as an + * integer. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + public static boolean putInt(ContentResolver cr, String name, int value) { + return putIntForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putIntForUser(ContentResolver cr, String name, int value, + int userId) { + return putStringForUser(cr, name, Integer.toString(value), userId); + } + + /** + * Convenience function for retrieving a single settings value + * as a {@code long}. Note that internally setting values are always + * stored as strings; this function converts the string to a {@code long} + * for you. The default value will be returned if the setting is + * not defined or not a {@code long}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid {@code long}. + */ + public static long getLong(ContentResolver cr, String name, long def) { + return getLongForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static long getLongForUser(ContentResolver cr, String name, long def, + int userId) { + String valString = getStringForUser(cr, name, userId); + long value; + try { + value = valString != null ? Long.parseLong(valString) : def; + } catch (NumberFormatException e) { + value = def; + } + return value; + } + + /** + * Convenience function for retrieving a single settings value + * as a {@code long}. Note that internally setting values are always + * stored as strings; this function converts the string to a {@code long} + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link CMSettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * + * @return The setting's current value. + * @throws CMSettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not an integer. + */ + public static long getLong(ContentResolver cr, String name) + throws CMSettingNotFoundException { + return getLongForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static long getLongForUser(ContentResolver cr, String name, int userId) + throws CMSettingNotFoundException { + String valString = getStringForUser(cr, name, userId); + try { + return Long.parseLong(valString); + } catch (NumberFormatException e) { + throw new CMSettingNotFoundException(name); + } + } + + /** + * Convenience function for updating a single settings value as a long + * integer. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + public static boolean putLong(ContentResolver cr, String name, long value) { + return putLongForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putLongForUser(ContentResolver cr, String name, long value, + int userId) { + return putStringForUser(cr, name, Long.toString(value), userId); + } + + /** + * Convenience function for retrieving a single settings value + * as a floating point number. Note that internally setting values are + * always stored as strings; this function converts the string to an + * float for you. The default value will be returned if the setting + * is not defined or not a valid float. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid float. + */ + public static float getFloat(ContentResolver cr, String name, float def) { + return getFloatForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static float getFloatForUser(ContentResolver cr, String name, float def, + int userId) { + String v = getStringForUser(cr, name, userId); + try { + return v != null ? Float.parseFloat(v) : def; + } catch (NumberFormatException e) { + return def; + } + } + + /** + * Convenience function for retrieving a single system settings value + * as a float. Note that internally setting values are always + * stored as strings; this function converts the string to a float + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link CMSettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * + * @throws CMSettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not a float. + * + * @return The setting's current value. + */ + public static float getFloat(ContentResolver cr, String name) + throws CMSettingNotFoundException { + return getFloatForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static float getFloatForUser(ContentResolver cr, String name, int userId) + throws CMSettingNotFoundException { + String v = getStringForUser(cr, name, userId); + if (v == null) { + throw new CMSettingNotFoundException(name); + } + try { + return Float.parseFloat(v); + } catch (NumberFormatException e) { + throw new CMSettingNotFoundException(name); + } + } + + /** + * Convenience function for updating a single settings value as a + * floating point number. This will either create a new entry in the + * table if the given name does not exist, or modify the value of the + * existing row with that name. Note that internally setting values + * are always stored as strings, so this function converts the given + * value to a string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + public static boolean putFloat(ContentResolver cr, String name, float value) { + return putFloatForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putFloatForUser(ContentResolver cr, String name, float value, + int userId) { + return putStringForUser(cr, name, Float.toString(value), userId); + } + + // endregion + + // region Secure Settings + + /** + * Whether to enable "advanced mode" for the current user. + * Boolean setting. 0 = no, 1 = yes. + * @hide + */ + public static final String ADVANCED_MODE = "advanced_mode"; + + /** + * The time in ms to keep the button backlight on after pressing a button. + * A value of 0 will keep the buttons on for as long as the screen is on. + * @hide + */ + public static final String BUTTON_BACKLIGHT_TIMEOUT = "button_backlight_timeout"; + + /** + * The button brightness to be used while the screen is on or after a button press, + * depending on the value of {@link BUTTON_BACKLIGHT_TIMEOUT}. + * Valid value range is between 0 and {@link PowerManager#getMaximumButtonBrightness()} + * @hide + */ + public static final String BUTTON_BRIGHTNESS = "button_brightness"; + + /** + * A '|' delimited list of theme components to apply from the default theme on first boot. + * Components can be one or more of the "mods_XXXXXXX" found in + * {@link ThemesContract$ThemesColumns}. Leaving this field blank assumes all components + * will be applied. + * + * ex: mods_icons|mods_overlays|mods_homescreen + * + * @hide + */ + public static final String DEFAULT_THEME_COMPONENTS = "default_theme_components"; + + /** + * Default theme to use. If empty, use holo. + * @hide + */ + public static final String DEFAULT_THEME_PACKAGE = "default_theme_package"; + + /** + * Developer options - Navigation Bar show switch + * @hide + */ + public static final String DEV_FORCE_SHOW_NAVBAR = "dev_force_show_navbar"; + + /** + * The keyboard brightness to be used while the screen is on. + * Valid value range is between 0 and {@link PowerManager#getMaximumKeyboardBrightness()} + * @hide + */ + public static final String KEYBOARD_BRIGHTNESS = "keyboard_brightness"; + + /** + * Custom navring actions + * @hide + */ + public static final String[] NAVIGATION_RING_TARGETS = new String[] { + "navigation_ring_targets_0", + "navigation_ring_targets_1", + "navigation_ring_targets_2", + }; + + /** + * String to contain power menu actions + * @hide + */ + public static final String POWER_MENU_ACTIONS = "power_menu_actions"; + + /** + * Whether to show the brightness slider in quick settings panel. + * @hide + */ + public static final String QS_SHOW_BRIGHTNESS_SLIDER = "qs_show_brightness_slider"; + + /** + * List of QS tile names + * @hide + */ + public static final String QS_TILES = "sysui_qs_tiles"; + + /** + * Use "main" tiles on the first row of the quick settings panel + * 0 = no, 1 = yes + * @hide + */ + public static final String QS_USE_MAIN_TILES = "sysui_qs_main_tiles"; + + /** + * Global stats collection + * @hide + */ + public static final String STATS_COLLECTION = "stats_collection"; + + /** + * Whether the global stats collection setting has been successfully reported to server + * @hide + */ + public static final String STATS_COLLECTION_REPORTED = "stats_collection_reported"; + + /** + * Whether newly installed apps should run with privacy guard by default + * @hide + */ + public static final String PRIVACY_GUARD_DEFAULT = "privacy_guard_default"; + + /** + * Whether a notification should be shown if privacy guard is enabled + * @hide + */ + public static final String PRIVACY_GUARD_NOTIFICATION = "privacy_guard_notification"; + + /** + * The global recents long press activity chosen by the user. + * This setting is stored as a flattened component name as + * per {@link ComponentName#flattenToString()}. + * + * @hide + */ + public static final String RECENTS_LONG_PRESS_ACTIVITY = "recents_long_press_activity"; + + /** + * What happens when the user presses the Home button when the + * phone is ringing.<br/> + * <b>Values:</b><br/> + * 1 - Nothing happens. (Default behavior)<br/> + * 2 - The Home button answer the current call.<br/> + * + * @hide + */ + public static final String RING_HOME_BUTTON_BEHAVIOR = "ring_home_button_behavior"; + + /** + * RING_HOME_BUTTON_BEHAVIOR value for "do nothing". + * @hide + */ + public static final int RING_HOME_BUTTON_BEHAVIOR_DO_NOTHING = 0x1; + + /** + * RING_HOME_BUTTON_BEHAVIOR value for "answer". + * @hide + */ + public static final int RING_HOME_BUTTON_BEHAVIOR_ANSWER = 0x2; + + /** + * RING_HOME_BUTTON_BEHAVIOR default value. + * @hide + */ + public static final int RING_HOME_BUTTON_BEHAVIOR_DEFAULT = + RING_HOME_BUTTON_BEHAVIOR_DO_NOTHING; + + /** + * Performance profile + * @hide + */ + public static final String PERFORMANCE_PROFILE = "performance_profile"; + + /** + * App-based performance profile selection + * @hide + */ + public static final String APP_PERFORMANCE_PROFILES_ENABLED = "app_perf_profiles_enabled"; + + /** + * Launch actions for left/right lockscreen targets + * @hide + */ + public static final String LOCKSCREEN_TARGETS = "lockscreen_target_actions"; + + /** + * Whether to display a menu containing 'Wipe data', 'Force close' and other options + * in the notification area and in the recent app list + * @hide + */ + public static final String DEVELOPMENT_SHORTCUT = "development_shortcut"; + + /** + * Whether to display the ADB notification. + * @hide + */ + public static final String ADB_NOTIFY = "adb_notify"; + + /** + * The TCP/IP port to run ADB on, or -1 for USB + * @hide + */ + public static final String ADB_PORT = "adb_port"; + + /** + * The hostname for this device + * @hide + */ + public static final String DEVICE_HOSTNAME = "device_hostname"; + + /** + * Whether to allow killing of the foreground app by long-pressing the Back button + * @hide + */ + public static final String KILL_APP_LONGPRESS_BACK = "kill_app_longpress_back"; + + /** Protected Components + * @hide + */ + public static final String PROTECTED_COMPONENTS = "protected_components"; + + /** + * Stored color matrix for LiveDisplay. This is used to allow co-existence with + * display tuning done by DisplayAdjustmentUtils when hardware support isn't + * available. + * @hide + */ + public static final String LIVE_DISPLAY_COLOR_MATRIX = "live_display_color_matrix"; + + /** + * Whether to include options in power menu for rebooting into recovery or bootloader + * @hide + */ + public static final String ADVANCED_REBOOT = "advanced_reboot"; + + /** + * This will be set to the system's current theme API version when ThemeService starts. + * It is useful for when an upgrade from one version of CM to another occurs. + * For example, after a user upgrades from CM11 to CM12, the value of this field + * might be 19. ThemeService would then change the value to 21. This is useful + * when an API change breaks a theme. Themeservice can identify old themes and + * unapply them from the system. + * @hide + */ + public static final String THEME_PREV_BOOT_API_LEVEL = "theme_prev_boot_api_level"; + + /** + * Whether detail view for the location tile is enabled + * @hide + */ + public static final String QS_LOCATION_ADVANCED = "qs_location_advanced"; + + /** + * Whether to show the keyguard visualizer. + * Boolean setting. 0 = off, 1 = on. + * @hide + */ + public static final String LOCKSCREEN_VISUALIZER_ENABLED = "lockscreen_visualizer"; + + /** + * Whether the lock screen is currently enabled/disabled by SystemUI (the QS tile likely). + * Boolean settings. 0 = off. 1 = on. + * @hide + */ + public static final String LOCKSCREEN_INTERNALLY_ENABLED = "lockscreen_internally_enabled"; + + /** + * Delimited list of packages allowed to manage/launch protected apps (used for filtering) + * @hide + */ + public static final String PROTECTED_COMPONENT_MANAGERS = "protected_component_managers"; + + /** + * Whether live lock screen is currently enabled/disabled by the user. + * Boolean settings. 0 = off, 1 = on + * @hide + */ + public static final String LIVE_LOCK_SCREEN_ENABLED = "live_lock_screen_enabled"; + + /** + * The user selected Live lock screen to display + * @hide + */ + public static final String DEFAULT_LIVE_LOCK_SCREEN_COMPONENT = + "default_live_lock_screen_component"; + + /** + * Whether keyguard will direct show security view (0 = false, 1 = true) + * @hide + */ + public static final String LOCK_PASS_TO_SECURITY_VIEW = "lock_screen_pass_to_security_view"; + + /** + * Whether touch hovering is enabled on supported hardware + * @hide + */ + public static final String FEATURE_TOUCH_HOVERING = "feature_touch_hovering"; + + /** + * Vibrator intensity setting for supported devices + * @hide + */ + public static final String VIBRATOR_INTENSITY = "vibrator_intensity"; + + /** + * Display gamma calibration values + * Suffix this with the control to set + * @hide + */ + public static final String DISPLAY_GAMMA_CALIBRATION_PREFIX = "display_gamma_"; + + /** + * Enabled live lockscreen components. Delimited by "|" + * @hide + */ + public static final String ENABLED_EVENT_LIVE_LOCKS_KEY = "live_lockscreens_events_enabled"; + + /** + * Current active & enabled Weather Provider Service + * + * @hide + */ + public static final String WEATHER_PROVIDER_SERVICE = "weather_provider_service"; + + // endregion + + /** + * I can haz more bukkits + * @hide + */ + public static final String __MAGICAL_TEST_PASSING_ENABLER = + "___magical_test_passing_enabler"; + + /** + * @hide + */ + public static final String[] LEGACY_SECURE_SETTINGS = new String[]{ + CMSettings.Secure.ADVANCED_MODE, + CMSettings.Secure.BUTTON_BACKLIGHT_TIMEOUT, + CMSettings.Secure.BUTTON_BRIGHTNESS, + CMSettings.Secure.DEFAULT_THEME_COMPONENTS, + CMSettings.Secure.DEFAULT_THEME_PACKAGE, + CMSettings.Secure.DEV_FORCE_SHOW_NAVBAR, + CMSettings.Secure.KEYBOARD_BRIGHTNESS, + CMSettings.Secure.POWER_MENU_ACTIONS, + CMSettings.Secure.STATS_COLLECTION, + CMSettings.Secure.QS_SHOW_BRIGHTNESS_SLIDER, + CMSettings.Secure.QS_TILES, + CMSettings.Secure.QS_USE_MAIN_TILES, + CMSettings.Secure.NAVIGATION_RING_TARGETS[0], + CMSettings.Secure.NAVIGATION_RING_TARGETS[1], + CMSettings.Secure.NAVIGATION_RING_TARGETS[2], + CMSettings.Secure.RECENTS_LONG_PRESS_ACTIVITY, + CMSettings.Secure.ADB_NOTIFY, + CMSettings.Secure.ADB_PORT, + CMSettings.Secure.DEVICE_HOSTNAME, + CMSettings.Secure.KILL_APP_LONGPRESS_BACK, + CMSettings.Secure.PROTECTED_COMPONENTS, + CMSettings.Secure.LIVE_DISPLAY_COLOR_MATRIX, + CMSettings.Secure.ADVANCED_REBOOT, + CMSettings.Secure.THEME_PREV_BOOT_API_LEVEL, + CMSettings.Secure.LOCKSCREEN_TARGETS, + CMSettings.Secure.RING_HOME_BUTTON_BEHAVIOR, + CMSettings.Secure.PRIVACY_GUARD_DEFAULT, + CMSettings.Secure.PRIVACY_GUARD_NOTIFICATION, + CMSettings.Secure.DEVELOPMENT_SHORTCUT, + CMSettings.Secure.PERFORMANCE_PROFILE, + CMSettings.Secure.APP_PERFORMANCE_PROFILES_ENABLED, + CMSettings.Secure.QS_LOCATION_ADVANCED, + CMSettings.Secure.LOCKSCREEN_VISUALIZER_ENABLED, + CMSettings.Secure.LOCK_PASS_TO_SECURITY_VIEW + }; + + /** + * @hide + */ + public static boolean isLegacySetting(String key) { + return ArrayUtils.contains(LEGACY_SECURE_SETTINGS, key); + } + + /** + * @hide + */ + public static final Validator PROTECTED_COMPONENTS_VALIDATOR = new Validator() { + private final String mDelimiter = "|"; + + @Override + public boolean validate(String value) { + if (!TextUtils.isEmpty(value)) { + final String[] array = TextUtils.split(value, Pattern.quote(mDelimiter)); + for (String item : array) { + if (TextUtils.isEmpty(item)) { + return false; // Empty components not allowed + } + } + } + return true; // Empty list is allowed though. + } + }; + + /** + * @hide + */ + public static final Validator PROTECTED_COMPONENTS_MANAGER_VALIDATOR = new Validator() { + private final String mDelimiter = "|"; + + @Override + public boolean validate(String value) { + if (!TextUtils.isEmpty(value)) { + final String[] array = TextUtils.split(value, Pattern.quote(mDelimiter)); + for (String item : array) { + if (TextUtils.isEmpty(item)) { + return false; // Empty components not allowed + } + } + } + return true; // Empty list is allowed though. + } + }; + + /** + * Mapping of validators for all secure settings. This map is used to validate both valid + * keys as well as validating the values for those keys. + * + * Note: Make sure if you add a new Secure setting you create a Validator for it and add + * it to this map. + * + * @hide + */ + public static final Map<String, Validator> VALIDATORS = + new ArrayMap<String, Validator>(); + static { + VALIDATORS.put(PROTECTED_COMPONENTS, PROTECTED_COMPONENTS_VALIDATOR); + VALIDATORS.put(PROTECTED_COMPONENT_MANAGERS, PROTECTED_COMPONENTS_MANAGER_VALIDATOR); + } + + /** + * @hide + */ + public static boolean shouldInterceptSystemProvider(String key) { + return false; + } + } + + /** + * Global settings, containing miscellaneous CM global preferences. This + * table holds simple name/value pairs. There are convenience + * functions for accessing individual settings entries. + */ + public static final class Global extends Settings.NameValueTable { + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global"); + + public static final String SYS_PROP_CM_SETTING_VERSION = "sys.cm_settings_global_version"; + + private static final NameValueCache sNameValueCache = new NameValueCache( + SYS_PROP_CM_SETTING_VERSION, + CONTENT_URI, + CALL_METHOD_GET_GLOBAL, + CALL_METHOD_PUT_GLOBAL); + + // region Methods + + + /** + * Put a delimited list as a string + * @param resolver to access the database with + * @param name to store + * @param delimiter to split + * @param list to join and store + * @hide + */ + public static void putListAsDelimitedString(ContentResolver resolver, String name, + String delimiter, List<String> list) { + String store = TextUtils.join(delimiter, list); + putString(resolver, name, store); + } + + /** + * Get a delimited string returned as a list + * @param resolver to access the database with + * @param name to store + * @param delimiter to split the list with + * @return list of strings for a specific Settings.Secure item + * @hide + */ + public static List<String> getDelimitedStringAsList(ContentResolver resolver, String name, + String delimiter) { + String baseString = getString(resolver, name); + List<String> list = new ArrayList<String>(); + if (!TextUtils.isEmpty(baseString)) { + final String[] array = TextUtils.split(baseString, Pattern.quote(delimiter)); + for (String item : array) { + if (TextUtils.isEmpty(item)) { + continue; + } + list.add(item); + } + } + return list; + } + + /** + * Construct the content URI for a particular name/value pair, useful for monitoring changes + * with a ContentObserver. + * @param name to look up in the table + * @return the corresponding content URI + */ + public static Uri getUriFor(String name) { + return Settings.NameValueTable.getUriFor(CONTENT_URI, name); + } + + /** + * Look up a name in the database. + * @param resolver to access the database with + * @param name to look up in the table + * @return the corresponding value, or null if not present + */ + public static String getString(ContentResolver resolver, String name) { + return getStringForUser(resolver, name, UserHandle.myUserId()); + } + + /** @hide */ + public static String getStringForUser(ContentResolver resolver, String name, + int userId) { + return sNameValueCache.getStringForUser(resolver, name, userId); + } + + /** + * Store a name/value pair into the database. + * @param resolver to access the database with + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + */ + public static boolean putString(ContentResolver resolver, String name, String value) { + return putStringForUser(resolver, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putStringForUser(ContentResolver resolver, String name, String value, + int userId) { + return sNameValueCache.putStringForUser(resolver, name, value, userId); + } + + /** + * Convenience function for retrieving a single settings value + * as an integer. Note that internally setting values are always + * stored as strings; this function converts the string to an integer + * for you. The default value will be returned if the setting is + * not defined or not an integer. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid integer. + */ + public static int getInt(ContentResolver cr, String name, int def) { + return getIntForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static int getIntForUser(ContentResolver cr, String name, int def, int userId) { + String v = getStringForUser(cr, name, userId); + try { + return v != null ? Integer.parseInt(v) : def; + } catch (NumberFormatException e) { + return def; + } + } + + /** + * Convenience function for retrieving a single settings value + * as an integer. Note that internally setting values are always + * stored as strings; this function converts the string to an integer + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link CMSettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * + * @throws CMSettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not an integer. + * + * @return The setting's current value. + */ + public static int getInt(ContentResolver cr, String name) + throws CMSettingNotFoundException { + return getIntForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static int getIntForUser(ContentResolver cr, String name, int userId) + throws CMSettingNotFoundException { + String v = getStringForUser(cr, name, userId); + try { + return Integer.parseInt(v); + } catch (NumberFormatException e) { + throw new CMSettingNotFoundException(name); + } + } + + /** + * Convenience function for updating a single settings value as an + * integer. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + public static boolean putInt(ContentResolver cr, String name, int value) { + return putIntForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putIntForUser(ContentResolver cr, String name, int value, + int userId) { + return putStringForUser(cr, name, Integer.toString(value), userId); + } + + /** + * Convenience function for retrieving a single settings value + * as a {@code long}. Note that internally setting values are always + * stored as strings; this function converts the string to a {@code long} + * for you. The default value will be returned if the setting is + * not defined or not a {@code long}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid {@code long}. + */ + public static long getLong(ContentResolver cr, String name, long def) { + return getLongForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static long getLongForUser(ContentResolver cr, String name, long def, + int userId) { + String valString = getStringForUser(cr, name, userId); + long value; + try { + value = valString != null ? Long.parseLong(valString) : def; + } catch (NumberFormatException e) { + value = def; + } + return value; + } + + /** + * Convenience function for retrieving a single settings value + * as a {@code long}. Note that internally setting values are always + * stored as strings; this function converts the string to a {@code long} + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link CMSettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * + * @return The setting's current value. + * @throws CMSettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not an integer. + */ + public static long getLong(ContentResolver cr, String name) + throws CMSettingNotFoundException { + return getLongForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static long getLongForUser(ContentResolver cr, String name, int userId) + throws CMSettingNotFoundException { + String valString = getStringForUser(cr, name, userId); + try { + return Long.parseLong(valString); + } catch (NumberFormatException e) { + throw new CMSettingNotFoundException(name); + } + } + + /** + * Convenience function for updating a single settings value as a long + * integer. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + public static boolean putLong(ContentResolver cr, String name, long value) { + return putLongForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putLongForUser(ContentResolver cr, String name, long value, + int userId) { + return putStringForUser(cr, name, Long.toString(value), userId); + } + + /** + * Convenience function for retrieving a single settings value + * as a floating point number. Note that internally setting values are + * always stored as strings; this function converts the string to an + * float for you. The default value will be returned if the setting + * is not defined or not a valid float. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid float. + */ + public static float getFloat(ContentResolver cr, String name, float def) { + return getFloatForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static float getFloatForUser(ContentResolver cr, String name, float def, + int userId) { + String v = getStringForUser(cr, name, userId); + try { + return v != null ? Float.parseFloat(v) : def; + } catch (NumberFormatException e) { + return def; + } + } + + /** + * Convenience function for retrieving a single system settings value + * as a float. Note that internally setting values are always + * stored as strings; this function converts the string to a float + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link CMSettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * + * @throws CMSettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not a float. + * + * @return The setting's current value. + */ + public static float getFloat(ContentResolver cr, String name) + throws CMSettingNotFoundException { + return getFloatForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static float getFloatForUser(ContentResolver cr, String name, int userId) + throws CMSettingNotFoundException { + String v = getStringForUser(cr, name, userId); + if (v == null) { + throw new CMSettingNotFoundException(name); + } + try { + return Float.parseFloat(v); + } catch (NumberFormatException e) { + throw new CMSettingNotFoundException(name); + } + } + + /** + * Convenience function for updating a single settings value as a + * floating point number. This will either create a new entry in the + * table if the given name does not exist, or modify the value of the + * existing row with that name. Note that internally setting values + * are always stored as strings, so this function converts the given + * value to a string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + public static boolean putFloat(ContentResolver cr, String name, float value) { + return putFloatForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putFloatForUser(ContentResolver cr, String name, float value, + int userId) { + return putStringForUser(cr, name, Float.toString(value), userId); + } + + // endregion + + // region Global Settings + /** + * Whether to wake the display when plugging or unplugging the charger + * + * @hide + */ + public static final String WAKE_WHEN_PLUGGED_OR_UNPLUGGED = + "wake_when_plugged_or_unplugged"; + + /** + * Whether to sound when charger power is connected/disconnected + * @hide + * @deprecated Use {@link android.provider.Settings.Global#CHARGING_SOUNDS_ENABLED} instead + */ + @Deprecated + public static final String POWER_NOTIFICATIONS_ENABLED = "power_notifications_enabled"; + + /** + * Whether to vibrate when charger power is connected/disconnected + * @hide + */ + public static final String POWER_NOTIFICATIONS_VIBRATE = "power_notifications_vibrate"; + + /** + * URI for power notification sounds + * @hide + */ + public static final String POWER_NOTIFICATIONS_RINGTONE = "power_notifications_ringtone"; + + /** + * @hide + */ + public static final String ZEN_DISABLE_DUCKING_DURING_MEDIA_PLAYBACK = + "zen_disable_ducking_during_media_playback"; + + /** + * Whether the system auto-configure the priority of the wifi ap's or use + * the manual settings established by the user. + * <> 0 to autoconfigure, 0 to manual settings. Default is <> 0. + * @hide + */ + public static final String WIFI_AUTO_PRIORITIES_CONFIGURATION = "wifi_auto_priority"; + // endregion + + /** + * @hide + */ + public static final String[] LEGACY_GLOBAL_SETTINGS = new String[]{ + CMSettings.Global.WAKE_WHEN_PLUGGED_OR_UNPLUGGED, + CMSettings.Global.POWER_NOTIFICATIONS_VIBRATE, + CMSettings.Global.POWER_NOTIFICATIONS_RINGTONE, + CMSettings.Global.ZEN_DISABLE_DUCKING_DURING_MEDIA_PLAYBACK, + CMSettings.Global.WIFI_AUTO_PRIORITIES_CONFIGURATION + }; + + /** + * @hide + */ + public static boolean isLegacySetting(String key) { + return ArrayUtils.contains(LEGACY_GLOBAL_SETTINGS, key); + } + + /** + * @hide + */ + public static boolean shouldInterceptSystemProvider(String key) { + return false; + } + } +} diff --git a/sdk/src/java/cyanogenmod/providers/DataUsageContract.java b/sdk/src/java/cyanogenmod/providers/DataUsageContract.java new file mode 100644 index 0000000..2371cd7 --- /dev/null +++ b/sdk/src/java/cyanogenmod/providers/DataUsageContract.java @@ -0,0 +1,164 @@ +/** + * Copyright (c) 2016, The CyanogenMod Project + * <p/> + * 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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.providers; + +import android.content.ContentResolver; +import android.net.Uri; + +/** + * <p> + * The DataUsageProvdier contract containing definitions for the supported URIs and columns + * </p> + */ + +public final class DataUsageContract { + + /** The authority for the DataUsage provider */ + public static final String DATAUSAGE_AUTHORITY = "org.cyanogenmod.providers.datausage"; + public static final String DATAUSAGE_TABLE = "datausage"; + + /** The content URI for the top-level datausage authority */ + public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + DATAUSAGE_AUTHORITY); + + /** The content URI for this table */ + public static final Uri CONTENT_URI = + Uri.withAppendedPath(BASE_CONTENT_URI, DATAUSAGE_TABLE); + + /** Define database columns */ + /** + * The unique ID for a row + * <P>Type: INTEGER </P> + */ + public static final String _ID = "_id"; + + /** + * The UID of the application whose bandwidth is stored in this row + * <P>Type: INTEGER </P> + */ + public static final String UID = "uid"; + + /** + * DataUsage Enable Configuration - statically set by the Settings App + * 0 - no data usage warning are generated + * 1 - data usage algorithm is evaluated and warnings are generated + * <P>Type: INTEGER</P> + */ + public static final String ENABLE = "enable"; + + /** + * DataUsage Active State - dynamically computed by the DataUsage algorithm to + * determine warning type to display to the user + * 0 - first warning type is generated, once a warning generation is triggered + * 1 - Nth warning type is generated, once a warning generation is triggered + * <P>Type: INTEGER</P> + */ + public static final String ACTIVE = "active"; + + /** + * The Name of the Application that corresponds to the uid + * <P>Type: TEXT</P> + */ + public static final String LABEL = "label"; + + /** + * Number of bytes consumed by the App so far. It is used to determine the number + * of bytes consumed between samples + * <P>Type: INTEGER (long) </P> + */ + public static final String BYTES = "bytes"; + + /** + * The slow bandwidth consumption average accumulated over 'SLOW' number of samples + * <P>Type: INTEGER (long)</P> + */ + public static final String SLOW_AVG = "slow_avg"; + + /** + * Number of slow samples accumulated so far, once the number of samples reaches a + * MAX number of samples, the 'slow_samples' pegs at MAX and new samples + * are factored into 'slow_avg' by "taking out" one sample. + * slow_samples < MAX: slow_avg = (slow_avg * slow_samples + new_sample)/(slow_samples+1) + * slow_samples == MAX: slow_avg = (slow_avg * (MAX-1) + new_sample)/MAX + * <P>Type: Integer (long></P> + */ + public static final String SLOW_SAMPLES = "slow_samples"; + + /** + * The fast bandwidth consumption average accumulated over 'fast' number of samples + * <P>Type: INTEGER (long)</P> + */ + public static final String FAST_AVG = "fast_avg"; + + /** + * Number of fast samples accumulated so far, analogous algorithm to 'slow_samples' + * <P>Type: INTEGER (long)</P> + */ + public static final String FAST_SAMPLES = "fast_samples"; + + /** + * Extra information used debugging purposes - collects up to 1000 samples so that + * algorithm can be manually verified + * <P>Type: TEXT</P> + */ + public static final String EXTRA = "extra"; + + /** + * The mime type of a directory of items + */ + public static final String CONTENT_TYPE = + ContentResolver.CURSOR_DIR_BASE_TYPE + "datausage_item"; + + /** + * The mime type of a single item + */ + public static final String CONTENT_ITEM_TYPE = + ContentResolver.CURSOR_ITEM_BASE_TYPE + "datausage_item"; + + /** + * A projection of all columns in the datausage table. + */ + public static final String [] PROJECTION_ALL = { + _ID, + UID, + ENABLE, + ACTIVE, + LABEL, + BYTES, + SLOW_AVG, + SLOW_SAMPLES, + FAST_AVG, + FAST_SAMPLES, + EXTRA + }; + + /** + * Column index for each field in the database row + */ + public static final int COLUMN_OF_ID = 0; + public static final int COLUMN_OF_UID = 1; + public static final int COLUMN_OF_ENABLE = 2; + public static final int COLUMN_OF_ACTIVE = 3; + public static final int COLUMN_OF_LABEL = 4; + public static final int COLUMN_OF_BYTES = 5; + public static final int COLUMN_OF_SLOW_AVG = 6; + public static final int COLUMN_OF_SLOW_SAMPLES = 7; + public static final int COLUMN_OF_FAST_AVG = 8; + public static final int COLUMN_OF_FAST_SAMPLES = 9; + public static final int COLUMN_OF_EXTRA = 10; + + +} diff --git a/sdk/src/java/cyanogenmod/providers/ThemesContract.java b/sdk/src/java/cyanogenmod/providers/ThemesContract.java new file mode 100644 index 0000000..4cdfeb9 --- /dev/null +++ b/sdk/src/java/cyanogenmod/providers/ThemesContract.java @@ -0,0 +1,717 @@ +/* + * 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.providers; + +import android.net.Uri; + +/** + * <p> + * The contract between the themes provider and applications. Contains + * definitions for the supported URIs and columns. + * </p> + */ +public class ThemesContract { + /** The authority for the themes provider */ + public static final String AUTHORITY = "com.cyanogenmod.themes"; + /** A content:// style uri to the authority for the themes provider */ + public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); + + public static class ThemesColumns { + public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "themes"); + + /** + * The unique ID for a row. + * <P>Type: INTEGER (long)</P> + */ + public static final String _ID = "_id"; + + /** + * The user visible title. + * <P>Type: TEXT</P> + */ + public static final String TITLE = "title"; + + /** + * Unique text to identify the apk pkg. ie "com.foo.bar" + * <P>Type: TEXT</P> + */ + public static final String PKG_NAME = "pkg_name"; + + /** + * A 32 bit RRGGBB color representative of the themes color scheme + * <P>Type: INTEGER</P> + */ + public static final String PRIMARY_COLOR = "primary_color"; + + /** + * A 2nd 32 bit RRGGBB color representative of the themes color scheme + * <P>Type: INTEGER</P> + */ + public static final String SECONDARY_COLOR = "secondary_color"; + + /** + * Name of the author of the theme + * <P>Type: TEXT</P> + */ + public static final String AUTHOR = "author"; + + /** + * The time that this row was created on its originating client (msecs + * since the epoch). + * <P>Type: INTEGER</P> + */ + public static final String DATE_CREATED = "created"; + + /** + * URI to an image that shows the homescreen with the theme applied + * since the epoch). + * <P>Type: TEXT</P> + */ + public static final String HOMESCREEN_URI = "homescreen_uri"; + + /** + * URI to an image that shows the lockscreen with theme applied + * <P>Type: TEXT</P> + */ + public static final String LOCKSCREEN_URI = "lockscreen_uri"; + + /** + * URI to an image that shows the style (aka skin) with theme applied + * <P>Type: TEXT</P> + */ + public static final String STYLE_URI = "style_uri"; + + /** + * TODO: Figure structure for actual animation instead of static + * URI to an image of the boot_anim. + * <P>Type: TEXT</P> + */ + public static final String BOOT_ANIM_URI = "bootanim_uri"; + + /** + * URI to an image of the status bar for this theme. + * <P>Type: TEXT</P> + */ + public static final String STATUSBAR_URI = "status_uri"; + + /** + * URI to an image of the fonts in this theme. + * <P>Type: TEXT</P> + */ + public static final String FONT_URI = "font_uri"; + + /** + * URI to an image of the fonts in this theme. + * <P>Type: TEXT</P> + */ + public static final String ICON_URI = "icon_uri"; + + /** + * URI to an image of the fonts in this theme. + * <P>Type: TEXT</P> + */ + public static final String OVERLAYS_URI = "overlays_uri"; + + /** + * 1 if theme modifies the launcher/homescreen else 0 + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String MODIFIES_LAUNCHER = "mods_homescreen"; + + /** + * 1 if theme modifies the lockscreen else 0 + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String MODIFIES_LOCKSCREEN = "mods_lockscreen"; + + /** + * 1 if theme modifies icons else 0 + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String MODIFIES_ICONS = "mods_icons"; + + /** + * 1 if theme modifies fonts + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String MODIFIES_FONTS = "mods_fonts"; + + /** + * 1 if theme modifies boot animation + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String MODIFIES_BOOT_ANIM = "mods_bootanim"; + + /** + * 1 if theme modifies notifications + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String MODIFIES_NOTIFICATIONS = "mods_notifications"; + + /** + * 1 if theme modifies alarm sounds + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String MODIFIES_ALARMS = "mods_alarms"; + + /** + * 1 if theme modifies ringtones + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String MODIFIES_RINGTONES = "mods_ringtones"; + + /** + * 1 if theme has overlays + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String MODIFIES_OVERLAYS = "mods_overlays"; + + /** + * 1 if theme has an overlay for SystemUI/StatusBar + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String MODIFIES_STATUS_BAR = "mods_status_bar"; + + /** + * 1 if theme has an overlay for SystemUI/NavBar + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String MODIFIES_NAVIGATION_BAR = "mods_navigation_bar"; + + /** + * 1 if theme has a live lock screen + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String MODIFIES_LIVE_LOCK_SCREEN = "mods_live_lock_screen"; + + /** + * URI to the theme's wallpaper. We should support multiple wallpaper + * but for now we will just have 1. + * <P>Type: TEXT</P> + */ + public static final String WALLPAPER_URI = "wallpaper_uri"; + + /** + * 1 if this row should actually be presented as a theme to the user. + * For example if a "theme" only modifies one component (ex icons) then + * we do not present it to the user under the themes table. + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String PRESENT_AS_THEME = "present_as_theme"; + + /** + * 1 if this theme is a legacy theme. + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String IS_LEGACY_THEME = "is_legacy_theme"; + + /** + * 1 if this theme is the system default theme. + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String IS_DEFAULT_THEME = "is_default_theme"; + + /** + * 1 if this theme is a legacy iconpack. A legacy icon pack is an APK that was written + * for Trebuchet or a 3rd party launcher. + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String IS_LEGACY_ICONPACK = "is_legacy_iconpack"; + + /** + * install/update time in millisecs. When the row is inserted this column + * is populated by the PackageInfo. It is used for syncing to PM + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String LAST_UPDATE_TIME = "updateTime"; + + /** + * install time in millisecs. When the row is inserted this column + * is populated by the PackageInfo. + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String INSTALL_TIME = "install_time"; + + /** + * The target API this theme supports + * is populated by the PackageInfo. + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String TARGET_API = "target_api"; + + /** + * The install state of the theme. + * Can be one of the following: + * {@link InstallState#UNKNOWN} + * {@link InstallState#INSTALLING} + * {@link InstallState#UPDATING} + * {@link InstallState#INSTALLED} + * <P>Type: INTEGER</P> + * <P>Default: 0</P> + */ + public static final String INSTALL_STATE = "install_state"; + + public static class InstallState { + public static final int UNKNOWN = 0; + public static final int INSTALLING = 1; + public static final int UPDATING = 2; + public static final int INSTALLED = 3; + } + } + + /** + * Key-value table which assigns a component (ex wallpaper) to a theme's package + */ + public static class MixnMatchColumns { + public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "mixnmatch"); + + /** + * The unique key for a row. See the KEY_* constants + * for valid examples + * <P>Type: TEXT</P> + */ + public static final String COL_KEY = "key"; + + /** + * The package name that corresponds to a given component. + * <P>Type: String</P> + */ + public static final String COL_VALUE = "value"; + + /** + * The package name that corresponds to where this component was applied from previously + * <P>Type: String</P> + */ + public static final String COL_PREV_VALUE = "previous_value"; + + /** + * Time when this entry was last updated + * <P>Type: INTEGER</P> + */ + public static final String COL_UPDATE_TIME = "update_time"; + + /* + * The unique ID for the component within a theme. + * Always 0 unless multiples of a component exist. + * <P>Type: INTEGER (long)</P> + */ + public static final String COL_COMPONENT_ID = "component_id"; + + /** + * Valid keys + */ + public static final String KEY_HOMESCREEN = "mixnmatch_homescreen"; + public static final String KEY_LOCKSCREEN = "mixnmatch_lockscreen"; + public static final String KEY_ICONS = "mixnmatch_icons"; + public static final String KEY_STATUS_BAR = "mixnmatch_status_bar"; + public static final String KEY_BOOT_ANIM = "mixnmatch_boot_anim"; + public static final String KEY_FONT = "mixnmatch_font"; + public static final String KEY_ALARM = "mixnmatch_alarm"; + public static final String KEY_NOTIFICATIONS = "mixnmatch_notifications"; + public static final String KEY_RINGTONE = "mixnmatch_ringtone"; + public static final String KEY_OVERLAYS = "mixnmatch_overlays"; + public static final String KEY_NAVIGATION_BAR = "mixnmatch_navigation_bar"; + public static final String KEY_LIVE_LOCK_SCREEN = "mixnmatch_live_lock_screen"; + + public static final String[] ROWS = { KEY_HOMESCREEN, + KEY_LOCKSCREEN, + KEY_ICONS, + KEY_STATUS_BAR, + KEY_BOOT_ANIM, + KEY_FONT, + KEY_NOTIFICATIONS, + KEY_RINGTONE, + KEY_ALARM, + KEY_OVERLAYS, + KEY_NAVIGATION_BAR, + KEY_LIVE_LOCK_SCREEN + }; + + /** + * For a given key value in the MixNMatch table, return the column + * associated with it in the Themes Table. This is useful for URI based + * elements like wallpaper where the caller wishes to determine the + * wallpaper URI. + */ + public static String componentToImageColName(String component) { + if (component.equals(MixnMatchColumns.KEY_HOMESCREEN)) { + return ThemesColumns.HOMESCREEN_URI; + } else if (component.equals(MixnMatchColumns.KEY_LOCKSCREEN)) { + return ThemesColumns.LOCKSCREEN_URI; + } else if (component.equals(MixnMatchColumns.KEY_BOOT_ANIM)) { + return ThemesColumns.BOOT_ANIM_URI; + } else if (component.equals(MixnMatchColumns.KEY_FONT)) { + return ThemesColumns.FONT_URI; + } else if (component.equals(MixnMatchColumns.KEY_ICONS)) { + return ThemesColumns.ICON_URI; + } else if (component.equals(MixnMatchColumns.KEY_STATUS_BAR)) { + return ThemesColumns.STATUSBAR_URI; + } else if (component.equals(MixnMatchColumns.KEY_NOTIFICATIONS)) { + throw new IllegalArgumentException("Notifications mixnmatch component does not have a related column"); + } else if (component.equals(MixnMatchColumns.KEY_RINGTONE)) { + throw new IllegalArgumentException("Ringtone mixnmatch component does not have a related column"); + } else if (component.equals(MixnMatchColumns.KEY_OVERLAYS)) { + return ThemesColumns.OVERLAYS_URI; + } else if (component.equals(MixnMatchColumns.KEY_STATUS_BAR)) { + throw new IllegalArgumentException( + "Status bar mixnmatch component does not have a related column"); + } else if (component.equals(MixnMatchColumns.KEY_NAVIGATION_BAR)) { + throw new IllegalArgumentException( + "Navigation bar mixnmatch component does not have a related column"); + } else if (component.equals(MixnMatchColumns.KEY_LIVE_LOCK_SCREEN)) { + throw new IllegalArgumentException( + "Live lock screen mixnmatch component does not have a related column"); + } + return null; + } + + /** + * A component in the themes table (IE "mods_wallpaper") has an + * equivalent key in mixnmatch table + */ + public static String componentToMixNMatchKey(String component) { + if (component.equals(ThemesColumns.MODIFIES_LAUNCHER)) { + return MixnMatchColumns.KEY_HOMESCREEN; + } else if (component.equals(ThemesColumns.MODIFIES_ICONS)) { + return MixnMatchColumns.KEY_ICONS; + } else if (component.equals(ThemesColumns.MODIFIES_LOCKSCREEN)) { + return MixnMatchColumns.KEY_LOCKSCREEN; + } else if (component.equals(ThemesColumns.MODIFIES_FONTS)) { + return MixnMatchColumns.KEY_FONT; + } else if (component.equals(ThemesColumns.MODIFIES_BOOT_ANIM)) { + return MixnMatchColumns.KEY_BOOT_ANIM; + } else if (component.equals(ThemesColumns.MODIFIES_ALARMS)) { + return MixnMatchColumns.KEY_ALARM; + } else if (component.equals(ThemesColumns.MODIFIES_NOTIFICATIONS)) { + return MixnMatchColumns.KEY_NOTIFICATIONS; + } else if (component.equals(ThemesColumns.MODIFIES_RINGTONES)) { + return MixnMatchColumns.KEY_RINGTONE; + } else if (component.equals(ThemesColumns.MODIFIES_OVERLAYS)) { + return MixnMatchColumns.KEY_OVERLAYS; + } else if (component.equals(ThemesColumns.MODIFIES_STATUS_BAR)) { + return MixnMatchColumns.KEY_STATUS_BAR; + } else if (component.equals(ThemesColumns.MODIFIES_NAVIGATION_BAR)) { + return MixnMatchColumns.KEY_NAVIGATION_BAR; + } else if (component.equals(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) { + return MixnMatchColumns.KEY_LIVE_LOCK_SCREEN; + } + return null; + } + + /** + * A mixnmatch key in has an + * equivalent value in the themes table + */ + public static String mixNMatchKeyToComponent(String mixnmatchKey) { + if (mixnmatchKey.equals(MixnMatchColumns.KEY_HOMESCREEN)) { + return ThemesColumns.MODIFIES_LAUNCHER; + } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_ICONS)) { + return ThemesColumns.MODIFIES_ICONS; + } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_LOCKSCREEN)) { + return ThemesColumns.MODIFIES_LOCKSCREEN; + } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_FONT)) { + return ThemesColumns.MODIFIES_FONTS; + } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_BOOT_ANIM)) { + return ThemesColumns.MODIFIES_BOOT_ANIM; + } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_ALARM)) { + return ThemesColumns.MODIFIES_ALARMS; + } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_NOTIFICATIONS)) { + return ThemesColumns.MODIFIES_NOTIFICATIONS; + } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_RINGTONE)) { + return ThemesColumns.MODIFIES_RINGTONES; + } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_OVERLAYS)) { + return ThemesColumns.MODIFIES_OVERLAYS; + } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_STATUS_BAR)) { + return ThemesColumns.MODIFIES_STATUS_BAR; + } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_NAVIGATION_BAR)) { + return ThemesColumns.MODIFIES_NAVIGATION_BAR; + } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_LIVE_LOCK_SCREEN)) { + return ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; + } + return null; + } + } + + /** + * Table containing cached preview files for a given theme + */ + public static class PreviewColumns { + /** + * Uri for retrieving the previews table. + * Querying the themes provider using this URI will return a cursor with a key and value + * columns, and a row for each component. + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "previews"); + + /** + * Uri for retrieving the previews for the currently applied components. + * Querying the themes provider using this URI will return a cursor with a single row + * containing all the previews for the components that are currently applied. + */ + public static final Uri APPLIED_URI = Uri.withAppendedPath(AUTHORITY_URI, + "applied_previews"); + + /** + * Uri for retrieving the default previews for the theme. + * Querying the themes provider using this URI will return a cursor with a single row + * containing all the previews for the default components of the current theme. + */ + public static final Uri COMPONENTS_URI = Uri.withAppendedPath(AUTHORITY_URI, + "components_previews"); + + /** + * The unique ID for a row. + * <P>Type: INTEGER (long)</P> + */ + public static final String _ID = "_id"; + + /** + * The unique ID for the theme these previews belong to. + * <P>Type: INTEGER (long)</P> + */ + public static final String THEME_ID = "theme_id"; + + /** + * The unique ID for the component within a theme. + * <P>Type: INTEGER (long)</P> + */ + public static final String COMPONENT_ID = "component_id"; + + /** + * The unique key for a row. See the Valid key constants section below + * for valid examples + * <P>Type: TEXT</P> + */ + public static final String COL_KEY = "key"; + + /** + * The package name that corresponds to a given component. + * <P>Type: String</P> + */ + public static final String COL_VALUE = "value"; + + /** + * Valid keys + */ + + /** + * Cached image of the themed status bar background. + * <P>Type: String (file path)</P> + */ + public static final String STATUSBAR_BACKGROUND = "statusbar_background"; + + /** + * Cached image of the themed bluetooth status icon. + * <P>Type: String (file path)</P> + */ + public static final String STATUSBAR_BLUETOOTH_ICON = "statusbar_bluetooth_icon"; + + /** + * Cached image of the themed wifi status icon. + * <P>Type: String (file path)</P> + */ + public static final String STATUSBAR_WIFI_ICON = "statusbar_wifi_icon"; + + /** + * Cached image of the themed cellular signal status icon. + * <P>Type: String (file path)</P> + */ + public static final String STATUSBAR_SIGNAL_ICON = "statusbar_signal_icon"; + + /** + * Cached image of the themed battery using portrait style. + * <P>Type: String (file path)</P> + */ + public static final String STATUSBAR_BATTERY_PORTRAIT = "statusbar_battery_portrait"; + + /** + * Cached image of the themed battery using landscape style. + * <P>Type: String (file path)</P> + */ + public static final String STATUSBAR_BATTERY_LANDSCAPE = "statusbar_battery_landscape"; + + /** + * Cached image of the themed battery using circle style. + * <P>Type: String (file path)</P> + */ + public static final String STATUSBAR_BATTERY_CIRCLE = "statusbar_battery_circle"; + + /** + * The themed color used for clock text in the status bar. + * <P>Type: INTEGER (int)</P> + */ + public static final String STATUSBAR_CLOCK_TEXT_COLOR = "statusbar_clock_text_color"; + + /** + * The themed margin value between the wifi and rssi signal icons. + * <P>Type: INTEGER (int)</P> + */ + public static final String STATUSBAR_WIFI_COMBO_MARGIN_END = "wifi_combo_margin_end"; + + /** + * Cached image of the themed navigation bar background. + * <P>Type: String (file path)</P> + */ + public static final String NAVBAR_BACKGROUND = "navbar_background"; + + /** + * Cached image of the themed back button. + * <P>Type: String (file path)</P> + */ + public static final String NAVBAR_BACK_BUTTON = "navbar_back_button"; + + /** + * Cached image of the themed home button. + * <P>Type: String (file path)</P> + */ + public static final String NAVBAR_HOME_BUTTON = "navbar_home_button"; + + /** + * Cached image of the themed recents button. + * <P>Type: String (file path)</P> + */ + public static final String NAVBAR_RECENT_BUTTON = "navbar_recent_button"; + + /** + * Cached image of the 1/3 icons + * <P>Type: String (file path)</P> + */ + public static final String ICON_PREVIEW_1 = "icon_preview_1"; + + /** + * Cached image of the 2/3 icons + * <P>Type: String (file path)</P> + */ + public static final String ICON_PREVIEW_2 = "icon_preview_2"; + + /** + * Cached image of the 3/3 icons + * <P>Type: String (file path)</P> + */ + public static final String ICON_PREVIEW_3 = "icon_preview_3"; + + /** + * Full path to the theme's wallpaper asset. + * <P>Type: String (file path)</P> + */ + public static final String WALLPAPER_FULL = "wallpaper_full"; + + /** + * Cached preview of the theme's wallpaper which is larger than the thumbnail + * but smaller than the full sized wallpaper. + * <P>Type: String (file path)</P> + */ + public static final String WALLPAPER_PREVIEW = "wallpaper_preview"; + + /** + * Cached thumbnail of the theme's wallpaper + * <P>Type: String (file path)</P> + */ + public static final String WALLPAPER_THUMBNAIL = "wallpaper_thumbnail"; + + /** + * Cached preview of the theme's lockscreen wallpaper which is larger than the thumbnail + * but smaller than the full sized lockscreen wallpaper. + * <P>Type: String (file path)</P> + */ + public static final String LOCK_WALLPAPER_PREVIEW = "lock_wallpaper_preview"; + + /** + * Cached thumbnail of the theme's lockscreen wallpaper + * <P>Type: String (file path)</P> + */ + public static final String LOCK_WALLPAPER_THUMBNAIL = "lock_wallpaper_thumbnail"; + + /** + * Cached preview of UI controls representing the theme's style + * <P>Type: String (file path)</P> + */ + public static final String STYLE_PREVIEW = "style_preview"; + + /** + * Cached thumbnail preview of UI controls representing the theme's style + * <P>Type: String (file path)</P> + */ + public static final String STYLE_THUMBNAIL = "style_thumbnail"; + + /** + * Cached thumbnail of the theme's boot animation + * <P>Type: String (file path)</P> + */ + public static final String BOOTANIMATION_THUMBNAIL = "bootanimation_thumbnail"; + + /** + * Cached preview of live lock screen + * <P>Type: String (file path)</P> + */ + public static final String LIVE_LOCK_SCREEN_PREVIEW = "live_lock_screen_preview"; + + /** + * Cached thumbnail preview of live lock screen + * <P>Type: String (file path)</P> + */ + public static final String LIVE_LOCK_SCREEN_THUMBNAIL = "live_lock_screen_thumbnail"; + + public static final String[] VALID_KEYS = { + STATUSBAR_BACKGROUND, + STATUSBAR_BLUETOOTH_ICON, + STATUSBAR_WIFI_ICON, + STATUSBAR_SIGNAL_ICON, + STATUSBAR_BATTERY_PORTRAIT, + STATUSBAR_BATTERY_LANDSCAPE, + STATUSBAR_BATTERY_CIRCLE, + STATUSBAR_CLOCK_TEXT_COLOR, + STATUSBAR_WIFI_COMBO_MARGIN_END, + NAVBAR_BACKGROUND, + NAVBAR_BACK_BUTTON, + NAVBAR_HOME_BUTTON, + NAVBAR_RECENT_BUTTON, + ICON_PREVIEW_1, + ICON_PREVIEW_2, + ICON_PREVIEW_3, + WALLPAPER_FULL, + WALLPAPER_PREVIEW, + WALLPAPER_THUMBNAIL, + LOCK_WALLPAPER_PREVIEW, + LOCK_WALLPAPER_THUMBNAIL, + STYLE_PREVIEW, + STYLE_THUMBNAIL, + BOOTANIMATION_THUMBNAIL, + LIVE_LOCK_SCREEN_PREVIEW, + LIVE_LOCK_SCREEN_THUMBNAIL, + }; + } +} diff --git a/sdk/src/java/cyanogenmod/providers/WeatherContract.java b/sdk/src/java/cyanogenmod/providers/WeatherContract.java new file mode 100644 index 0000000..e8e3726 --- /dev/null +++ b/sdk/src/java/cyanogenmod/providers/WeatherContract.java @@ -0,0 +1,240 @@ +/* + * 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.providers; + +import android.net.Uri; + +/** + * The contract between the weather provider and applications. + */ +public class WeatherContract { + + /** + * The authority of the weather content provider + */ + public static final String AUTHORITY = "com.cyanogenmod.weather"; + + /** + * A content:// style uri to the authority for the weather provider + */ + public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); + + public static class WeatherColumns { + public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "weather"); + + public static final Uri CURRENT_AND_FORECAST_WEATHER_URI + = Uri.withAppendedPath(CONTENT_URI, "current_and_forecast"); + public static final Uri CURRENT_WEATHER_URI + = Uri.withAppendedPath(CONTENT_URI, "current"); + public static final Uri FORECAST_WEATHER_URI + = Uri.withAppendedPath(CONTENT_URI, "forecast"); + + /** + * A unique ID for the city. NOTE: this value fully depends on the implementation of the + * weather provider service and can potentially change when you switch providers. + * <P>Type: TEXT</P> + */ + public static final String CURRENT_CITY_ID = "city_id"; + + /** + * The city name + * <P>Type: TEXT</P> + */ + public static final String CURRENT_CITY = "city"; + + /** + * A Valid {@link WeatherCode} + * <P>Type: INTEGER</P> + */ + public static final String CURRENT_CONDITION_CODE = "condition_code"; + + + /** + * A localized string mapped to the current weather condition code. Note that, if no + * locale is found, the string will be in english + * <P>Type: TEXT</P> + */ + public static final String CURRENT_CONDITION = "condition"; + + /** + * The current weather temperature + * <P>Type: FLOAT</P> + */ + public static final String CURRENT_TEMPERATURE = "temperature"; + + /** + * The unit in which current temperature is reported + * <P>Type: INTEGER</P> + * Can be one of the following: + * <ul> + * <li>{@link TempUnit#CELSIUS}</li> + * <li>{@link TempUnit#FAHRENHEIT}</li> + * </ul> + */ + public static final String CURRENT_TEMPERATURE_UNIT = "temperature_unit"; + + /** + * The current weather humidity + * <P>Type: FLOAT</P> + */ + public static final String CURRENT_HUMIDITY = "humidity"; + + /** + * The current wind direction (in degrees) + * <P>Type: FLOAT</P> + */ + public static final String CURRENT_WIND_DIRECTION = "wind_direction"; + + /** + * The current wind speed + * <P>Type: FLOAT</P> + */ + public static final String CURRENT_WIND_SPEED = "wind_speed"; + + /** + * The unit in which the wind speed is reported + * <P>Type: INTEGER</P> + * Can be one of the following: + * <ul> + * <li>{@link WindSpeedUnit#KPH}</li> + * <li>{@link WindSpeedUnit#MPH}</li> + * </ul> + */ + public static final String CURRENT_WIND_SPEED_UNIT = "wind_speed_unit"; + + /** + * The timestamp when this weather was reported + * <P>Type: LONG</P> + */ + public static final String CURRENT_TIMESTAMP = "timestamp"; + + /** + * The forecasted low temperature + * <P>Type: FLOAT</P> + */ + public static final String FORECAST_LOW = "forecast_low"; + + /** + * The forecasted high temperature + * <P>Type: FLOAT</P> + */ + public static final String FORECAST_HIGH = "forecast_high"; + + /** + * A localized string mapped to the forecasted weather condition code. Note that, if no + * locale is found, the string will be in english + * <P>Type: TEXT</P> + */ + public static final String FORECAST_CONDITION = "forecast_condition"; + + /** + * The code identifying the forecasted weather condition. + * @see #CURRENT_CONDITION_CODE + */ + public static final String FORECAST_CONDITION_CODE = "forecast_condition_code"; + + /** + * Temperature units + */ + public static final class TempUnit { + private TempUnit() {} + public final static int CELSIUS = 1; + public final static int FAHRENHEIT = 2; + } + + /** + * Wind speed units + */ + public static final class WindSpeedUnit { + private WindSpeedUnit() {} + /** + * Kilometers per hour + */ + public final static int KPH = 1; + + /** + * Miles per hour + */ + public final static int MPH = 2; + } + + /** + * Weather condition codes + */ + public static final class WeatherCode { + private WeatherCode() {} + + /** + * @hide + */ + public final static int WEATHER_CODE_MIN = 0; + + public final static int TORNADO = 0; + public final static int TROPICAL_STORM = 1; + public final static int HURRICANE = 2; + public final static int SEVERE_THUNDERSTORMS = 3; + public final static int THUNDERSTORMS = 4; + public final static int MIXED_RAIN_AND_SNOW = 5; + public final static int MIXED_RAIN_AND_SLEET = 6; + public final static int MIXED_SNOW_AND_SLEET = 7; + public final static int FREEZING_DRIZZLE = 8; + public final static int DRIZZLE = 9; + public final static int FREEZING_RAIN = 10; + public final static int SHOWERS = 11; + public final static int SNOW_FLURRIES = 12; + public final static int LIGHT_SNOW_SHOWERS = 13; + public final static int BLOWING_SNOW = 14; + public final static int SNOW = 15; + public final static int HAIL = 16; + public final static int SLEET = 17; + public final static int DUST = 18; + public final static int FOGGY = 19; + public final static int HAZE = 20; + public final static int SMOKY = 21; + public final static int BLUSTERY = 22; + public final static int WINDY = 23; + public final static int COLD = 24; + public final static int CLOUDY = 25; + public final static int MOSTLY_CLOUDY_NIGHT = 26; + public final static int MOSTLY_CLOUDY_DAY = 27; + public final static int PARTLY_CLOUDY_NIGHT = 28; + public final static int PARTLY_CLOUDY_DAY = 29; + public final static int CLEAR_NIGHT = 30; + public final static int SUNNY = 31; + public final static int FAIR_NIGHT = 32; + public final static int FAIR_DAY = 33; + public final static int MIXED_RAIN_AND_HAIL = 34; + public final static int HOT = 35; + public final static int ISOLATED_THUNDERSTORMS = 36; + public final static int SCATTERED_THUNDERSTORMS = 37; + public final static int SCATTERED_SHOWERS = 38; + public final static int HEAVY_SNOW = 39; + public final static int SCATTERED_SNOW_SHOWERS = 40; + public final static int PARTLY_CLOUDY = 41; + public final static int THUNDERSHOWER = 42; + public final static int SNOW_SHOWERS = 43; + public final static int ISOLATED_THUNDERSHOWERS = 44; + + /** + * @hide + */ + public final static int WEATHER_CODE_MAX = 44; + + public final static int NOT_AVAILABLE = 3200; + } + } +}
\ No newline at end of file diff --git a/sdk/src/java/cyanogenmod/themes/IThemeChangeListener.aidl b/sdk/src/java/cyanogenmod/themes/IThemeChangeListener.aidl new file mode 100644 index 0000000..0700eb6 --- /dev/null +++ b/sdk/src/java/cyanogenmod/themes/IThemeChangeListener.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2014-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.themes; + +/** {@hide} */ +oneway interface IThemeChangeListener { + void onProgress(int progress); + void onFinish(boolean isSuccess); +} diff --git a/sdk/src/java/cyanogenmod/themes/IThemeProcessingListener.aidl b/sdk/src/java/cyanogenmod/themes/IThemeProcessingListener.aidl new file mode 100644 index 0000000..648e1a9 --- /dev/null +++ b/sdk/src/java/cyanogenmod/themes/IThemeProcessingListener.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014-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.themes; + +/** {@hide} */ +oneway interface IThemeProcessingListener { + void onFinishedProcessing(String pkgName); +} diff --git a/sdk/src/java/cyanogenmod/themes/IThemeService.aidl b/sdk/src/java/cyanogenmod/themes/IThemeService.aidl new file mode 100644 index 0000000..fa186e9 --- /dev/null +++ b/sdk/src/java/cyanogenmod/themes/IThemeService.aidl @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014-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.themes; + +import cyanogenmod.themes.IThemeChangeListener; +import cyanogenmod.themes.IThemeProcessingListener; +import cyanogenmod.themes.ThemeChangeRequest; + +import java.util.Map; + +/** {@hide} */ +interface IThemeService { + oneway void requestThemeChangeUpdates(in IThemeChangeListener listener); + oneway void removeUpdates(in IThemeChangeListener listener); + + oneway void requestThemeChange(in ThemeChangeRequest request, boolean removePerAppThemes); + oneway void applyDefaultTheme(); + boolean isThemeApplying(); + int getProgress(); + + boolean processThemeResources(String themePkgName); + boolean isThemeBeingProcessed(String themePkgName); + oneway void registerThemeProcessingListener(in IThemeProcessingListener listener); + oneway void unregisterThemeProcessingListener(in IThemeProcessingListener listener); + + oneway void rebuildResourceCache(); + + long getLastThemeChangeTime(); + int getLastThemeChangeRequestType(); +} diff --git a/sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.aidl b/sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.aidl new file mode 100644 index 0000000..e1d9e4f --- /dev/null +++ b/sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015-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.themes; + +parcelable ThemeChangeRequest; diff --git a/sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.java b/sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.java new file mode 100644 index 0000000..f8eeeb5 --- /dev/null +++ b/sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2015-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.themes; + +import android.content.pm.ThemeUtils; +import android.content.res.ThemeConfig; +import android.os.Parcel; +import android.os.Parcelable; + +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static cyanogenmod.providers.ThemesContract.ThemesColumns.*; + +public final class ThemeChangeRequest implements Parcelable { + public static final int DEFAULT_WALLPAPER_ID = -1; + + private final Map<String, String> mThemeComponents = new HashMap<>(); + private final Map<String, String> mPerAppOverlays = new HashMap<>(); + private RequestType mRequestType; + private long mWallpaperId = -1; + + public String getOverlayThemePackageName() { + return getThemePackageNameForComponent(MODIFIES_OVERLAYS); + } + + public String getStatusBarThemePackageName() { + return getThemePackageNameForComponent(MODIFIES_STATUS_BAR); + } + + public String getNavBarThemePackageName() { + return getThemePackageNameForComponent(MODIFIES_NAVIGATION_BAR); + } + + public String getFontThemePackageName() { + return getThemePackageNameForComponent(MODIFIES_FONTS); + } + + public String getIconsThemePackageName() { + return getThemePackageNameForComponent(MODIFIES_ICONS); + } + + public String getBootanimationThemePackageName() { + return getThemePackageNameForComponent(MODIFIES_BOOT_ANIM); + } + + public String getWallpaperThemePackageName() { + return getThemePackageNameForComponent(MODIFIES_LAUNCHER); + } + + public String getLockWallpaperThemePackageName() { + return getThemePackageNameForComponent(MODIFIES_LOCKSCREEN); + } + + public String getAlarmThemePackageName() { + return getThemePackageNameForComponent(MODIFIES_ALARMS); + } + + public String getNotificationThemePackageName() { + return getThemePackageNameForComponent(MODIFIES_NOTIFICATIONS); + } + + public String getRingtoneThemePackageName() { + return getThemePackageNameForComponent(MODIFIES_RINGTONES); + } + + public String getLiveLockScreenThemePackageName() { + return getThemePackageNameForComponent(MODIFIES_LIVE_LOCK_SCREEN); + } + + public final Map<String, String> getThemeComponentsMap() { + return Collections.unmodifiableMap(mThemeComponents); + } + + public long getWallpaperId() { + return mWallpaperId; + } + + /** + * Get the mapping for per app themes + * @return A mapping of apps and the theme to apply for each one. or null if none set. + */ + public final Map<String, String> getPerAppOverlays() { + return Collections.unmodifiableMap(mPerAppOverlays); + } + + public int getNumChangesRequested() { + return mThemeComponents.size() + mPerAppOverlays.size(); + } + + public RequestType getReqeustType() { + return mRequestType; + } + + private String getThemePackageNameForComponent(String componentName) { + return mThemeComponents.get(componentName); + } + + private ThemeChangeRequest(Map<String, String> components, Map<String, String> perAppThemes, + RequestType requestType, long wallpaperId) { + if (components != null) { + mThemeComponents.putAll(components); + } + if (perAppThemes != null) { + mPerAppOverlays.putAll(perAppThemes); + } + mRequestType = requestType; + mWallpaperId = wallpaperId; + } + + private ThemeChangeRequest(Parcel source) { + // Read parcelable version via the Concierge + ParcelInfo parcelInfo = Concierge.receiveParcel(source); + int parcelableVersion = parcelInfo.getParcelVersion(); + + int numComponents = source.readInt(); + for (int i = 0; i < numComponents; i++) { + mThemeComponents.put(source.readString(), source.readString()); + } + + numComponents = source.readInt(); + for (int i = 0 ; i < numComponents; i++) { + mPerAppOverlays.put(source.readString(), source.readString()); + } + mRequestType = RequestType.values()[source.readInt()]; + mWallpaperId = source.readLong(); + + // 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(mThemeComponents.size()); + for (String component : mThemeComponents.keySet()) { + dest.writeString(component); + dest.writeString(mThemeComponents.get(component)); + } + dest.writeInt((mPerAppOverlays.size())); + for (String appPkgName : mPerAppOverlays.keySet()) { + dest.writeString(appPkgName); + dest.writeString(mPerAppOverlays.get(appPkgName)); + } + dest.writeInt(mRequestType.ordinal()); + dest.writeLong(mWallpaperId); + + // Complete the parcel info for the concierge + parcelInfo.complete(); + } + + public static final Parcelable.Creator<ThemeChangeRequest> CREATOR = + new Parcelable.Creator<ThemeChangeRequest>() { + @Override + public ThemeChangeRequest createFromParcel(Parcel source) { + return new ThemeChangeRequest(source); + } + + @Override + public ThemeChangeRequest[] newArray(int size) { + return new ThemeChangeRequest[size]; + } + }; + + public enum RequestType { + USER_REQUEST, + USER_REQUEST_MIXNMATCH, + THEME_UPDATED, + THEME_REMOVED, + THEME_RESET + } + + public static class Builder { + Map<String, String> mThemeComponents = new HashMap<>(); + Map<String, String> mPerAppOverlays = new HashMap<>(); + RequestType mRequestType = RequestType.USER_REQUEST; + long mWallpaperId; + + public Builder() {} + + public Builder(ThemeConfig themeConfig) { + if (themeConfig != null) { + buildChangeRequestFromThemeConfig(themeConfig); + } + } + + public Builder setOverlay(String pkgName) { + return setComponent(MODIFIES_OVERLAYS, pkgName); + } + + public Builder setStatusBar(String pkgName) { + return setComponent(MODIFIES_STATUS_BAR, pkgName); + } + + public Builder setNavBar(String pkgName) { + return setComponent(MODIFIES_NAVIGATION_BAR, pkgName); + } + + public Builder setFont(String pkgName) { + return setComponent(MODIFIES_FONTS, pkgName); + } + + public Builder setIcons(String pkgName) { + return setComponent(MODIFIES_ICONS, pkgName); + } + + public Builder setBootanimation(String pkgName) { + return setComponent(MODIFIES_BOOT_ANIM, pkgName); + } + + public Builder setWallpaper(String pkgName) { + return setComponent(MODIFIES_LAUNCHER, pkgName); + } + + // Used in the case that more than one wallpaper exists for a given pkg name + public Builder setWallpaperId(long id) { + mWallpaperId = id; + return this; + } + + public Builder setLockWallpaper(String pkgName) { + return setComponent(MODIFIES_LOCKSCREEN, pkgName); + } + + public Builder setAlarm(String pkgName) { + return setComponent(MODIFIES_ALARMS, pkgName); + } + + public Builder setNotification(String pkgName) { + return setComponent(MODIFIES_NOTIFICATIONS, pkgName); + } + + public Builder setRingtone(String pkgName) { + return setComponent(MODIFIES_RINGTONES, pkgName); + } + + public Builder setLiveLockScreen(String pkgName) { + return setComponent(MODIFIES_LIVE_LOCK_SCREEN, pkgName); + } + + public Builder setComponent(String component, String pkgName) { + if (pkgName != null) { + mThemeComponents.put(component, pkgName); + } else { + mThemeComponents.remove(component); + } + return this; + } + + public Builder setAppOverlay(String appPkgName, String themePkgName) { + if (appPkgName != null) { + if (themePkgName != null) { + mPerAppOverlays.put(appPkgName, themePkgName); + } else { + mPerAppOverlays.remove(appPkgName); + } + } + + return this; + } + + public Builder setRequestType(RequestType requestType) { + mRequestType = requestType != null ? requestType : RequestType.USER_REQUEST; + return this; + } + + public ThemeChangeRequest build() { + return new ThemeChangeRequest(mThemeComponents, mPerAppOverlays, + mRequestType, mWallpaperId); + } + + private void buildChangeRequestFromThemeConfig(ThemeConfig themeConfig) { + if (themeConfig.getFontPkgName() != null) { + this.setFont(themeConfig.getFontPkgName()); + } + if (themeConfig.getIconPackPkgName() != null) { + this.setIcons(themeConfig.getIconPackPkgName()); + } + if (themeConfig.getOverlayPkgName() != null) { + this.setOverlay(themeConfig.getOverlayPkgName()); + } + if (themeConfig.getOverlayForStatusBar() != null) { + this.setStatusBar(themeConfig.getOverlayForStatusBar()); + } + if (themeConfig.getOverlayForNavBar() != null) { + this.setNavBar(themeConfig.getOverlayForNavBar()); + } + + // Check if there are any per-app overlays using this theme + final Map<String, ThemeConfig.AppTheme> themes = themeConfig.getAppThemes(); + for (String appPkgName : themes.keySet()) { + if (ThemeUtils.isPerAppThemeComponent(appPkgName)) { + this.setAppOverlay(appPkgName, themes.get(appPkgName).getOverlayPkgName()); + } + } + } + } +} diff --git a/sdk/src/java/cyanogenmod/themes/ThemeManager.java b/sdk/src/java/cyanogenmod/themes/ThemeManager.java new file mode 100644 index 0000000..5fbbde7 --- /dev/null +++ b/sdk/src/java/cyanogenmod/themes/ThemeManager.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2014-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.themes; + +import android.content.Context; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.ArraySet; +import android.util.Log; + +import cyanogenmod.app.CMContextConstants; +import cyanogenmod.themes.ThemeChangeRequest.RequestType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Manages changing and applying of themes. + * <p>Get an instance of this class by calling blah blah blah</p> + */ +public class ThemeManager { + private static final String TAG = ThemeManager.class.getName(); + private static IThemeService sService; + private static ThemeManager sInstance; + private static Handler mHandler; + + private Set<ThemeChangeListener> mChangeListeners = new ArraySet<>(); + + private Set<ThemeProcessingListener> mProcessingListeners = new ArraySet<>(); + + private ThemeManager(Context context) { + sService = getService(); + if (context.getPackageManager().hasSystemFeature( + CMContextConstants.Features.THEMES) && sService == null) { + throw new RuntimeException("Unable to get ThemeManagerService. The service either" + + " crashed, was not started, or the interface has been called to early in" + + " SystemServer init"); + } + mHandler = new Handler(Looper.getMainLooper()); + } + + public static ThemeManager getInstance(Context context) { + if (sInstance == null) { + sInstance = new ThemeManager(context); + } + + return sInstance; + } + + /** @hide */ + public static IThemeService getService() { + if (sService != null) { + return sService; + } + IBinder b = ServiceManager.getService(CMContextConstants.CM_THEME_SERVICE); + if (b != null) { + sService = IThemeService.Stub.asInterface(b); + return sService; + } + return null; + } + + private final IThemeChangeListener mThemeChangeListener = new IThemeChangeListener.Stub() { + @Override + public void onProgress(final int progress) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (mChangeListeners) { + List<ThemeChangeListener> listenersToRemove = new ArrayList<>(); + for (ThemeChangeListener listener : mChangeListeners) { + try { + listener.onProgress(progress); + } catch (Throwable e) { + Log.w(TAG, "Unable to update theme change progress", e); + listenersToRemove.add(listener); + } + } + if (listenersToRemove.size() > 0) { + for (ThemeChangeListener listener : listenersToRemove) { + mChangeListeners.remove(listener); + } + } + } + } + }); + } + + @Override + public void onFinish(final boolean isSuccess) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (mChangeListeners) { + List<ThemeChangeListener> listenersToRemove = new ArrayList<>(); + for (ThemeChangeListener listener : mChangeListeners) { + try { + listener.onFinish(isSuccess); + } catch (Throwable e) { + Log.w(TAG, "Unable to update theme change listener", e); + listenersToRemove.add(listener); + } + } + if (listenersToRemove.size() > 0) { + for (ThemeChangeListener listener : listenersToRemove) { + mChangeListeners.remove(listener); + } + } + } + } + }); + } + }; + + private final IThemeProcessingListener mThemeProcessingListener = + new IThemeProcessingListener.Stub() { + @Override + public void onFinishedProcessing(final String pkgName) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (mProcessingListeners) { + List<ThemeProcessingListener> listenersToRemove = new ArrayList<>(); + for (ThemeProcessingListener listener : mProcessingListeners) { + try { + listener.onFinishedProcessing(pkgName); + } catch (Throwable e) { + Log.w(TAG, "Unable to update theme change progress", e); + listenersToRemove.add(listener); + } + } + if (listenersToRemove.size() > 0) { + for (ThemeProcessingListener listener : listenersToRemove) { + mProcessingListeners.remove(listener); + } + } + } + } + }); + } + }; + + + /** + * @deprecated Use {@link ThemeManager#registerThemeChangeListener(ThemeChangeListener)} instead + */ + public void addClient(ThemeChangeListener listener) { + registerThemeChangeListener(listener); + } + + /** + * @deprecated Use {@link ThemeManager#unregisterThemeChangeListener(ThemeChangeListener)} + * instead + */ + public void removeClient(ThemeChangeListener listener) { + unregisterThemeChangeListener(listener); + } + + /** + * @deprecated Use {@link ThemeManager#unregisterThemeChangeListener(ThemeChangeListener)} + * instead + */ + public void onClientPaused(ThemeChangeListener listener) { + unregisterThemeChangeListener(listener); + } + + /** + * @deprecated Use {@link ThemeManager#registerThemeChangeListener(ThemeChangeListener)} instead + */ + public void onClientResumed(ThemeChangeListener listener) { + registerThemeChangeListener(listener); + } + + /** + * @deprecated Use {@link ThemeManager#unregisterThemeChangeListener(ThemeChangeListener)} + * instead + */ + public void onClientDestroyed(ThemeChangeListener listener) { + unregisterThemeChangeListener(listener); + } + + /** + * Register a {@link ThemeChangeListener} to be notified when a theme is done being processed. + * @param listener {@link ThemeChangeListener} to register + */ + public void registerThemeChangeListener(ThemeChangeListener listener) { + synchronized (mChangeListeners) { + if (mChangeListeners.contains(listener)) { + throw new IllegalArgumentException("Listener already registered"); + } + if (mChangeListeners.size() == 0) { + try { + sService.requestThemeChangeUpdates(mThemeChangeListener); + } catch (RemoteException e) { + Log.w(TAG, "Unable to register listener", e); + } + } + mChangeListeners.add(listener); + } + } + + /** + * Unregister a {@link ThemeChangeListener} + * @param listener {@link ThemeChangeListener} to unregister + */ + public void unregisterThemeChangeListener(ThemeChangeListener listener) { + synchronized (mChangeListeners) { + mChangeListeners.remove(listener); + if (mChangeListeners.size() == 0) { + try { + sService.removeUpdates(mThemeChangeListener); + } catch (RemoteException e) { + Log.w(TAG, "Unable to unregister listener", e); + } + } + } + } + + /** + * Register a {@link ThemeProcessingListener} to be notified when a theme is done being + * processed. + * @param listener {@link ThemeProcessingListener} to register + */ + public void registerProcessingListener(ThemeProcessingListener listener) { + synchronized (mProcessingListeners) { + if (mProcessingListeners.contains(listener)) { + throw new IllegalArgumentException("Listener already registered"); + } + if (mProcessingListeners.size() == 0) { + try { + sService.registerThemeProcessingListener(mThemeProcessingListener); + } catch (RemoteException e) { + Log.w(TAG, "Unable to register listener", e); + } + } + mProcessingListeners.add(listener); + } + } + + /** + * Unregister a {@link ThemeProcessingListener}. + * @param listener {@link ThemeProcessingListener} to unregister + */ + public void unregisterProcessingListener(ThemeProcessingListener listener) { + synchronized (mProcessingListeners) { + mProcessingListeners.remove(listener); + if (mProcessingListeners.size() == 0) { + try { + sService.unregisterThemeProcessingListener(mThemeProcessingListener); + } catch (RemoteException e) { + Log.w(TAG, "Unable to unregister listener", e); + } + } + } + } + + public void requestThemeChange(String pkgName, List<String> components) { + requestThemeChange(pkgName, components, true); + } + + public void requestThemeChange(String pkgName, List<String> components, + boolean removePerAppThemes) { + Map<String, String> componentMap = new HashMap<>(components.size()); + for (String component : components) { + componentMap.put(component, pkgName); + } + requestThemeChange(componentMap, removePerAppThemes); + } + + public void requestThemeChange(Map<String, String> componentMap) { + requestThemeChange(componentMap, true); + } + + public void requestThemeChange(Map<String, String> componentMap, boolean removePerAppThemes) { + ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); + for (String component : componentMap.keySet()) { + builder.setComponent(component, componentMap.get(component)); + } + + requestThemeChange(builder.build(), removePerAppThemes); + } + + public void requestThemeChange(ThemeChangeRequest request, boolean removePerAppThemes) { + try { + sService.requestThemeChange(request, removePerAppThemes); + } catch (RemoteException e) { + logThemeServiceException(e); + } + } + + public void applyDefaultTheme() { + try { + sService.applyDefaultTheme(); + } catch (RemoteException e) { + logThemeServiceException(e); + } + } + + public boolean isThemeApplying() { + try { + return sService.isThemeApplying(); + } catch (RemoteException e) { + logThemeServiceException(e); + } + + return false; + } + + public boolean isThemeBeingProcessed(String themePkgName) { + try { + return sService.isThemeBeingProcessed(themePkgName); + } catch (RemoteException e) { + logThemeServiceException(e); + } + return false; + } + + public int getProgress() { + try { + return sService.getProgress(); + } catch (RemoteException e) { + logThemeServiceException(e); + } + return -1; + } + + public boolean processThemeResources(String themePkgName) { + try { + return sService.processThemeResources(themePkgName); + } catch (RemoteException e) { + logThemeServiceException(e); + } + return false; + } + + public long getLastThemeChangeTime() { + try { + return sService.getLastThemeChangeTime(); + } catch (RemoteException e) { + logThemeServiceException(e); + } + return 0; + } + + public ThemeChangeRequest.RequestType getLastThemeChangeRequestType() { + try { + int type = sService.getLastThemeChangeRequestType(); + return (type >= 0 && type < RequestType.values().length) + ? RequestType.values()[type] + : null; + } catch (RemoteException e) { + logThemeServiceException(e); + } + + return null; + } + + private void logThemeServiceException(Exception e) { + Log.w(TAG, "Unable to access ThemeService", e); + } + + public interface ThemeChangeListener { + void onProgress(int progress); + void onFinish(boolean isSuccess); + } + + public interface ThemeProcessingListener { + void onFinishedProcessing(String pkgName); + } +} + diff --git a/sdk/src/java/cyanogenmod/util/ColorUtils.java b/sdk/src/java/cyanogenmod/util/ColorUtils.java new file mode 100644 index 0000000..0142396 --- /dev/null +++ b/sdk/src/java/cyanogenmod/util/ColorUtils.java @@ -0,0 +1,526 @@ +/* + * Copyright (c) 2011-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.util; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.MathUtils; + +import com.android.internal.util.cm.palette.Palette; + +import java.util.Collections; +import java.util.Comparator; + +/** + * Helper class for colorspace conversions, and color-related + * algorithms which may be generally useful. + */ +public class ColorUtils { + + private static int[] SOLID_COLORS = new int[] { + Color.RED, 0xFFFFA500, Color.YELLOW, Color.GREEN, Color.CYAN, + Color.BLUE, Color.MAGENTA, Color.WHITE, Color.BLACK + }; + + /** + * Drop the alpha component from an RGBA packed int and return + * a non sign-extended RGB int. + * + * @param rgba + * @return rgb + */ + public static int dropAlpha(int rgba) { + return rgba & 0x00FFFFFF; + } + + /** + * Converts an RGB packed int into L*a*b space, which is well-suited for finding + * perceptual differences in color + * + * @param rgb A 32-bit value of packed RGB ints + * @return array of Lab values of size 3 + */ + public static float[] convertRGBtoLAB(int rgb) { + float[] lab = new float[3]; + float fx, fy, fz; + float eps = 216.f / 24389.f; + float k = 24389.f / 27.f; + + float Xr = 0.964221f; // reference white D50 + float Yr = 1.0f; + float Zr = 0.825211f; + + // RGB to XYZ + float r = Color.red(rgb) / 255.f; //R 0..1 + float g = Color.green(rgb) / 255.f; //G 0..1 + float b = Color.blue(rgb) / 255.f; //B 0..1 + + // assuming sRGB (D65) + if (r <= 0.04045) + r = r / 12; + else + r = (float) Math.pow((r + 0.055) / 1.055, 2.4); + + if (g <= 0.04045) + g = g / 12; + else + g = (float) Math.pow((g + 0.055) / 1.055, 2.4); + + if (b <= 0.04045) + b = b / 12; + else + b = (float) Math.pow((b + 0.055) / 1.055, 2.4); + + float X = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b; + float Y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b; + float Z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b; + + // XYZ to Lab + float xr = X / Xr; + float yr = Y / Yr; + float zr = Z / Zr; + + if (xr > eps) + fx = (float) Math.pow(xr, 1 / 3.); + else + fx = (float) ((k * xr + 16.) / 116.); + + if (yr > eps) + fy = (float) Math.pow(yr, 1 / 3.); + else + fy = (float) ((k * yr + 16.) / 116.); + + if (zr > eps) + fz = (float) Math.pow(zr, 1 / 3.); + else + fz = (float) ((k * zr + 16.) / 116); + + float Ls = (116 * fy) - 16; + float as = 500 * (fx - fy); + float bs = 200 * (fy - fz); + + lab[0] = (2.55f * Ls + .5f); + lab[1] = (as + .5f); + lab[2] = (bs + .5f); + + return lab; + } + + /** + * Calculate the colour difference value between two colours in lab space. + * This code is from OpenIMAJ under BSD License + * + * @param L1 first colour's L component + * @param a1 first colour's a component + * @param b1 first colour's b component + * @param L2 second colour's L component + * @param a2 second colour's a component + * @param b2 second colour's b component + * @return the CIE 2000 colour difference + */ + public static double calculateDeltaE(double L1, double a1, double b1, + double L2, double a2, double b2) { + double Lmean = (L1 + L2) / 2.0; + double C1 = Math.sqrt(a1 * a1 + b1 * b1); + double C2 = Math.sqrt(a2 * a2 + b2 * b2); + double Cmean = (C1 + C2) / 2.0; + + double G = (1 - Math.sqrt(Math.pow(Cmean, 7) / (Math.pow(Cmean, 7) + Math.pow(25, 7)))) / 2; + double a1prime = a1 * (1 + G); + double a2prime = a2 * (1 + G); + + double C1prime = Math.sqrt(a1prime * a1prime + b1 * b1); + double C2prime = Math.sqrt(a2prime * a2prime + b2 * b2); + double Cmeanprime = (C1prime + C2prime) / 2; + + double h1prime = Math.atan2(b1, a1prime) + + 2 * Math.PI * (Math.atan2(b1, a1prime) < 0 ? 1 : 0); + double h2prime = Math.atan2(b2, a2prime) + + 2 * Math.PI * (Math.atan2(b2, a2prime) < 0 ? 1 : 0); + double Hmeanprime = ((Math.abs(h1prime - h2prime) > Math.PI) + ? (h1prime + h2prime + 2 * Math.PI) / 2 : (h1prime + h2prime) / 2); + + double T = 1.0 - 0.17 * Math.cos(Hmeanprime - Math.PI / 6.0) + + 0.24 * Math.cos(2 * Hmeanprime) + 0.32 * Math.cos(3 * Hmeanprime + Math.PI / 30) + - 0.2 * Math.cos(4 * Hmeanprime - 21 * Math.PI / 60); + + double deltahprime = ((Math.abs(h1prime - h2prime) <= Math.PI) ? h2prime - h1prime + : (h2prime <= h1prime) ? h2prime - h1prime + 2 * Math.PI + : h2prime - h1prime - 2 * Math.PI); + + double deltaLprime = L2 - L1; + double deltaCprime = C2prime - C1prime; + double deltaHprime = 2.0 * Math.sqrt(C1prime * C2prime) * Math.sin(deltahprime / 2.0); + double SL = 1.0 + ((0.015 * (Lmean - 50) * (Lmean - 50)) + / (Math.sqrt(20 + (Lmean - 50) * (Lmean - 50)))); + double SC = 1.0 + 0.045 * Cmeanprime; + double SH = 1.0 + 0.015 * Cmeanprime * T; + + double deltaTheta = (30 * Math.PI / 180) + * Math.exp(-((180 / Math.PI * Hmeanprime - 275) / 25) + * ((180 / Math.PI * Hmeanprime - 275) / 25)); + double RC = (2 + * Math.sqrt(Math.pow(Cmeanprime, 7) / (Math.pow(Cmeanprime, 7) + Math.pow(25, 7)))); + double RT = (-RC * Math.sin(2 * deltaTheta)); + + double KL = 1; + double KC = 1; + double KH = 1; + + double deltaE = Math.sqrt( + ((deltaLprime / (KL * SL)) * (deltaLprime / (KL * SL))) + + ((deltaCprime / (KC * SC)) * (deltaCprime / (KC * SC))) + + ((deltaHprime / (KH * SH)) * (deltaHprime / (KH * SH))) + + (RT * (deltaCprime / (KC * SC)) * (deltaHprime / (KH * SH)))); + + return deltaE; + } + + /** + * Finds the "perceptually nearest" color from a list of colors to + * the given RGB value. This is done by converting to + * L*a*b colorspace and using the CIE2000 deltaE algorithm. + * + * @param rgb The original color to start with + * @param colors An array of colors to test + * @return RGB packed int of nearest color in the list + */ + public static int findPerceptuallyNearestColor(int rgb, int[] colors) { + int nearestColor = 0; + double closest = Double.MAX_VALUE; + + float[] original = convertRGBtoLAB(rgb); + + for (int i = 0; i < colors.length; i++) { + float[] cl = convertRGBtoLAB(colors[i]); + double deltaE = calculateDeltaE(original[0], original[1], original[2], + cl[0], cl[1], cl[2]); + if (deltaE < closest) { + nearestColor = colors[i]; + closest = deltaE; + } + } + return nearestColor; + } + + /** + * Convenience method to find the nearest "solid" color (having RGB components + * of either 0 or 255) to the given color. This is useful for cases such as + * LED notification lights which may not be able to display the full range + * of colors due to hardware limitations. + * + * @param rgb + * @return the perceptually nearest color in RGB + */ + public static int findPerceptuallyNearestSolidColor(int rgb) { + return findPerceptuallyNearestColor(rgb, SOLID_COLORS); + } + + /** + * Given a Palette, pick out the dominant swatch based on population + * + * @param palette + * @return the dominant Swatch + */ + public static Palette.Swatch getDominantSwatch(Palette palette) { + if (palette == null || palette.getSwatches().size() == 0) { + return null; + } + // find most-represented swatch based on population + return Collections.max(palette.getSwatches(), new Comparator<Palette.Swatch>() { + @Override + public int compare(Palette.Swatch sw1, Palette.Swatch sw2) { + return Integer.compare(sw1.getPopulation(), sw2.getPopulation()); + } + }); + } + + /** + * Takes a drawable and uses Palette to generate a suitable "alert" + * color which can be used for an external notification mechanism + * such as an RGB LED. This will always pick a solid color having + * RGB components of 255 or 0. + * + * @param drawable The drawable to generate a color for + * @return a suitable solid color which corresponds to the image + */ + public static int generateAlertColorFromDrawable(Drawable drawable) { + int alertColor = Color.BLACK; + Bitmap bitmap = null; + + if (drawable == null) { + return alertColor; + } + + if (drawable instanceof BitmapDrawable) { + bitmap = ((BitmapDrawable) drawable).getBitmap(); + } else { + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + } + + if (bitmap != null) { + Palette p = Palette.from(bitmap).generate(); + if (p == null) { + return alertColor; + } + + // First try the dominant color + final Palette.Swatch dominantSwatch = getDominantSwatch(p); + int iconColor = alertColor; + if (dominantSwatch != null) { + iconColor = dominantSwatch.getRgb(); + alertColor = findPerceptuallyNearestSolidColor(iconColor); + } + + // Try the most saturated color if we got white or black (boring) + if (alertColor == Color.BLACK || alertColor == Color.WHITE) { + iconColor = p.getVibrantColor(Color.WHITE); + alertColor = findPerceptuallyNearestSolidColor(iconColor); + } + + if (!(drawable instanceof BitmapDrawable)) { + bitmap.recycle(); + } + } + + return alertColor; + } + + /** + * Convert a color temperature value (in Kelvin) to a RGB units as floats. + * This can be used in a transform matrix or hardware gamma control. + * + * @param tempK + * @return + */ + public static float[] temperatureToRGB(int degreesK) { + int k = MathUtils.constrain(degreesK, 1000, 20000); + float a = (k % 100) / 100.0f; + int i = ((k - 1000)/ 100) * 3; + + return new float[] { interp(i, a), interp(i+1, a), interp(i+2, a) }; + } + + private static float interp(int i, float a) { + return MathUtils.lerp((float)sColorTable[i], (float)sColorTable[i+3], a); + } + + /** + * This table is a modified version of the original blackbody chart, found here: + * http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html + * + * Created by Ingo Thiel. + */ + private static final double[] sColorTable = new double[] { + 1.00000000, 0.18172716, 0.00000000, + 1.00000000, 0.25503671, 0.00000000, + 1.00000000, 0.30942099, 0.00000000, + 1.00000000, 0.35357379, 0.00000000, + 1.00000000, 0.39091524, 0.00000000, + 1.00000000, 0.42322816, 0.00000000, + 1.00000000, 0.45159884, 0.00000000, + 1.00000000, 0.47675916, 0.00000000, + 1.00000000, 0.49923747, 0.00000000, + 1.00000000, 0.51943421, 0.00000000, + 1.00000000, 0.54360078, 0.08679949, + 1.00000000, 0.56618736, 0.14065513, + 1.00000000, 0.58734976, 0.18362641, + 1.00000000, 0.60724493, 0.22137978, + 1.00000000, 0.62600248, 0.25591950, + 1.00000000, 0.64373109, 0.28819679, + 1.00000000, 0.66052319, 0.31873863, + 1.00000000, 0.67645822, 0.34786758, + 1.00000000, 0.69160518, 0.37579588, + 1.00000000, 0.70602449, 0.40267128, + 1.00000000, 0.71976951, 0.42860152, + 1.00000000, 0.73288760, 0.45366838, + 1.00000000, 0.74542112, 0.47793608, + 1.00000000, 0.75740814, 0.50145662, + 1.00000000, 0.76888303, 0.52427322, + 1.00000000, 0.77987699, 0.54642268, + 1.00000000, 0.79041843, 0.56793692, + 1.00000000, 0.80053332, 0.58884417, + 1.00000000, 0.81024551, 0.60916971, + 1.00000000, 0.81957693, 0.62893653, + 1.00000000, 0.82854786, 0.64816570, + 1.00000000, 0.83717703, 0.66687674, + 1.00000000, 0.84548188, 0.68508786, + 1.00000000, 0.85347859, 0.70281616, + 1.00000000, 0.86118227, 0.72007777, + 1.00000000, 0.86860704, 0.73688797, + 1.00000000, 0.87576611, 0.75326132, + 1.00000000, 0.88267187, 0.76921169, + 1.00000000, 0.88933596, 0.78475236, + 1.00000000, 0.89576933, 0.79989606, + 1.00000000, 0.90198230, 0.81465502, + 1.00000000, 0.90963069, 0.82838210, + 1.00000000, 0.91710889, 0.84190889, + 1.00000000, 0.92441842, 0.85523742, + 1.00000000, 0.93156127, 0.86836903, + 1.00000000, 0.93853986, 0.88130458, + 1.00000000, 0.94535695, 0.89404470, + 1.00000000, 0.95201559, 0.90658983, + 1.00000000, 0.95851906, 0.91894041, + 1.00000000, 0.96487079, 0.93109690, + 1.00000000, 0.97107439, 0.94305985, + 1.00000000, 0.97713351, 0.95482993, + 1.00000000, 0.98305189, 0.96640795, + 1.00000000, 0.98883326, 0.97779486, + 1.00000000, 0.99448139, 0.98899179, + 1.00000000, 1.00000000, 1.00000000, + 0.98947904, 0.99348723, 1.00000000, + 0.97940448, 0.98722715, 1.00000000, + 0.96975025, 0.98120637, 1.00000000, + 0.96049223, 0.97541240, 1.00000000, + 0.95160805, 0.96983355, 1.00000000, + 0.94303638, 0.96443333, 1.00000000, + 0.93480451, 0.95923080, 1.00000000, + 0.92689056, 0.95421394, 1.00000000, + 0.91927697, 0.94937330, 1.00000000, + 0.91194747, 0.94470005, 1.00000000, + 0.90488690, 0.94018594, 1.00000000, + 0.89808115, 0.93582323, 1.00000000, + 0.89151710, 0.93160469, 1.00000000, + 0.88518247, 0.92752354, 1.00000000, + 0.87906581, 0.92357340, 1.00000000, + 0.87315640, 0.91974827, 1.00000000, + 0.86744421, 0.91604254, 1.00000000, + 0.86191983, 0.91245088, 1.00000000, + 0.85657444, 0.90896831, 1.00000000, + 0.85139976, 0.90559011, 1.00000000, + 0.84638799, 0.90231183, 1.00000000, + 0.84153180, 0.89912926, 1.00000000, + 0.83682430, 0.89603843, 1.00000000, + 0.83225897, 0.89303558, 1.00000000, + 0.82782969, 0.89011714, 1.00000000, + 0.82353066, 0.88727974, 1.00000000, + 0.81935641, 0.88452017, 1.00000000, + 0.81530175, 0.88183541, 1.00000000, + 0.81136180, 0.87922257, 1.00000000, + 0.80753191, 0.87667891, 1.00000000, + 0.80380769, 0.87420182, 1.00000000, + 0.80018497, 0.87178882, 1.00000000, + 0.79665980, 0.86943756, 1.00000000, + 0.79322843, 0.86714579, 1.00000000, + 0.78988728, 0.86491137, 1.00000000, + 0.78663296, 0.86273225, 1.00000000, + 0.78346225, 0.86060650, 1.00000000, + 0.78037207, 0.85853224, 1.00000000, + 0.77735950, 0.85650771, 1.00000000, + 0.77442176, 0.85453121, 1.00000000, + 0.77155617, 0.85260112, 1.00000000, + 0.76876022, 0.85071588, 1.00000000, + 0.76603147, 0.84887402, 1.00000000, + 0.76336762, 0.84707411, 1.00000000, + 0.76076645, 0.84531479, 1.00000000, + 0.75822586, 0.84359476, 1.00000000, + 0.75574383, 0.84191277, 1.00000000, + 0.75331843, 0.84026762, 1.00000000, + 0.75094780, 0.83865816, 1.00000000, + 0.74863017, 0.83708329, 1.00000000, + 0.74636386, 0.83554194, 1.00000000, + 0.74414722, 0.83403311, 1.00000000, + 0.74197871, 0.83255582, 1.00000000, + 0.73985682, 0.83110912, 1.00000000, + 0.73778012, 0.82969211, 1.00000000, + 0.73574723, 0.82830393, 1.00000000, + 0.73375683, 0.82694373, 1.00000000, + 0.73180765, 0.82561071, 1.00000000, + 0.72989845, 0.82430410, 1.00000000, + 0.72802807, 0.82302316, 1.00000000, + 0.72619537, 0.82176715, 1.00000000, + 0.72439927, 0.82053539, 1.00000000, + 0.72263872, 0.81932722, 1.00000000, + 0.72091270, 0.81814197, 1.00000000, + 0.71922025, 0.81697905, 1.00000000, + 0.71756043, 0.81583783, 1.00000000, + 0.71593234, 0.81471775, 1.00000000, + 0.71433510, 0.81361825, 1.00000000, + 0.71276788, 0.81253878, 1.00000000, + 0.71122987, 0.81147883, 1.00000000, + 0.70972029, 0.81043789, 1.00000000, + 0.70823838, 0.80941546, 1.00000000, + 0.70678342, 0.80841109, 1.00000000, + 0.70535469, 0.80742432, 1.00000000, + 0.70395153, 0.80645469, 1.00000000, + 0.70257327, 0.80550180, 1.00000000, + 0.70121928, 0.80456522, 1.00000000, + 0.69988894, 0.80364455, 1.00000000, + 0.69858167, 0.80273941, 1.00000000, + 0.69729688, 0.80184943, 1.00000000, + 0.69603402, 0.80097423, 1.00000000, + 0.69479255, 0.80011347, 1.00000000, + 0.69357196, 0.79926681, 1.00000000, + 0.69237173, 0.79843391, 1.00000000, + 0.69119138, 0.79761446, 1.00000000, + 0.69003044, 0.79680814, 1.00000000, + 0.68888844, 0.79601466, 1.00000000, + 0.68776494, 0.79523371, 1.00000000, + 0.68665951, 0.79446502, 1.00000000, + 0.68557173, 0.79370830, 1.00000000, + 0.68450119, 0.79296330, 1.00000000, + 0.68344751, 0.79222975, 1.00000000, + 0.68241029, 0.79150740, 1.00000000, + 0.68138918, 0.79079600, 1.00000000, + 0.68038380, 0.79009531, 1.00000000, + 0.67939381, 0.78940511, 1.00000000, + 0.67841888, 0.78872517, 1.00000000, + 0.67745866, 0.78805526, 1.00000000, + 0.67651284, 0.78739518, 1.00000000, + 0.67558112, 0.78674472, 1.00000000, + 0.67466317, 0.78610368, 1.00000000, + 0.67375872, 0.78547186, 1.00000000, + 0.67286748, 0.78484907, 1.00000000, + 0.67198916, 0.78423512, 1.00000000, + 0.67112350, 0.78362984, 1.00000000, + 0.67027024, 0.78303305, 1.00000000, + 0.66942911, 0.78244457, 1.00000000, + 0.66859988, 0.78186425, 1.00000000, + 0.66778228, 0.78129191, 1.00000000, + 0.66697610, 0.78072740, 1.00000000, + 0.66618110, 0.78017057, 1.00000000, + 0.66539706, 0.77962127, 1.00000000, + 0.66462376, 0.77907934, 1.00000000, + 0.66386098, 0.77854465, 1.00000000, + 0.66310852, 0.77801705, 1.00000000, + 0.66236618, 0.77749642, 1.00000000, + 0.66163375, 0.77698261, 1.00000000, + 0.66091106, 0.77647551, 1.00000000, + 0.66019791, 0.77597498, 1.00000000, + 0.65949412, 0.77548090, 1.00000000, + 0.65879952, 0.77499315, 1.00000000, + 0.65811392, 0.77451161, 1.00000000, + 0.65743716, 0.77403618, 1.00000000, + 0.65676908, 0.77356673, 1.00000000, + 0.65610952, 0.77310316, 1.00000000, + 0.65545831, 0.77264537, 1.00000000, + 0.65481530, 0.77219324, 1.00000000, + 0.65418036, 0.77174669, 1.00000000, + 0.65355332, 0.77130560, 1.00000000, + 0.65293404, 0.77086988, 1.00000000, + 0.65232240, 0.77043944, 1.00000000, + 0.65171824, 0.77001419, 1.00000000, + 0.65112144, 0.76959404, 1.00000000, + 0.65053187, 0.76917889, 1.00000000, + 0.64994941, 0.76876866, 1.00000000, + 0.64937392, 0.76836326, 1.00000000 + }; + +} diff --git a/sdk/src/java/cyanogenmod/weather/CMWeatherManager.java b/sdk/src/java/cyanogenmod/weather/CMWeatherManager.java new file mode 100644 index 0000000..32eab52 --- /dev/null +++ b/sdk/src/java/cyanogenmod/weather/CMWeatherManager.java @@ -0,0 +1,373 @@ +/* + * 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.weather; + +import android.annotation.NonNull; +import android.content.ComponentName; +import android.content.Context; +import android.location.Location; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.ArraySet; +import cyanogenmod.app.CMContextConstants; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Provides access to the weather services in the device. + */ +public class CMWeatherManager { + + private static ICMWeatherManager sWeatherManagerService; + private static CMWeatherManager sInstance; + private Context mContext; + private Map<RequestInfo,WeatherUpdateRequestListener> mWeatherUpdateRequestListeners + = Collections.synchronizedMap(new HashMap<RequestInfo,WeatherUpdateRequestListener>()); + private Map<RequestInfo,LookupCityRequestListener> mLookupNameRequestListeners + = Collections.synchronizedMap(new HashMap<RequestInfo,LookupCityRequestListener>()); + private Handler mHandler; + private Set<WeatherServiceProviderChangeListener> mProviderChangedListeners = new ArraySet<>(); + + private static final String TAG = CMWeatherManager.class.getSimpleName(); + + /** + * Weather update request state: Successfully completed + */ + public static final int WEATHER_REQUEST_COMPLETED = 1; + + /** + * Weather update request state: You need to wait a bit longer before requesting an update + * again. + * <p>Please bear in mind that the weather does not change very often. A threshold of 10 minutes + * is enforced by the system</p> + */ + public static final int WEATHER_REQUEST_SUBMITTED_TOO_SOON = -1; + + /** + * Weather update request state: An error occurred while trying to update the weather. You + * should wait before trying again, or your request will be rejected with + * {@link #WEATHER_REQUEST_SUBMITTED_TOO_SOON} + */ + public static final int WEATHER_REQUEST_FAILED = -2; + + /** + * Weather update request state: Only one update request can be processed at a given time. + */ + public static final int WEATHER_REQUEST_ALREADY_IN_PROGRESS = -3; + + /** @hide */ + public static final int LOOKUP_REQUEST_COMPLETED = 100; + + /** @hide */ + public static final int LOOKUP_REQUEST_FAILED = -100; + + /** @hide */ + public static final int LOOKUP_REQUEST_NO_MATCH_FOUND = -101; + + + private CMWeatherManager(Context context) { + Context appContext = context.getApplicationContext(); + mContext = (appContext != null) ? appContext : context; + sWeatherManagerService = getService(); + + if (context.getPackageManager().hasSystemFeature( + CMContextConstants.Features.WEATHER_SERVICES) && (sWeatherManagerService == null)) { + throw new RuntimeException("Unable to bind the CMWeatherManagerService"); + } + mHandler = new Handler(appContext.getMainLooper()); + } + + /** + * Gets or creates an instance of the {@link cyanogenmod.weather.CMWeatherManager} + * @param context + * @return {@link CMWeatherManager} + */ + public static CMWeatherManager getInstance(Context context) { + if (sInstance == null) { + sInstance = new CMWeatherManager(context); + } + return sInstance; + } + + /** @hide */ + public static ICMWeatherManager getService() { + if (sWeatherManagerService != null) { + return sWeatherManagerService; + } + IBinder binder = ServiceManager.getService(CMContextConstants.CM_WEATHER_SERVICE); + if (binder != null) { + sWeatherManagerService = ICMWeatherManager.Stub.asInterface(binder); + return sWeatherManagerService; + } + return null; + } + + /** + * Forces the weather service to request the latest available weather information for + * the supplied {@link android.location.Location} location. + * + * @param location The location you want to get the latest weather data from. + * @param listener {@link WeatherUpdateRequestListener} To be notified once the active weather + * service provider has finished + * processing your request + */ + public void requestWeatherUpdate(@NonNull Location location, + @NonNull WeatherUpdateRequestListener listener) { + if (sWeatherManagerService == null) { + return; + } + + try { + RequestInfo info = new RequestInfo + .Builder(mRequestInfoListener) + .setLocation(location) + .build(); + if (listener != null) mWeatherUpdateRequestListeners.put(info, listener); + sWeatherManagerService.updateWeather(info); + } catch (RemoteException e) { + } + } + + /** + * Forces the weather service to request the latest weather information for the provided + * WeatherLocation. This is the preferred method for requesting a weather update. + * + * @param weatherLocation A {@link cyanogenmod.weather.WeatherLocation} that was previously + * obtained by calling + * {@link #lookupCity(String, LookupCityRequestListener)} + * @param listener {@link WeatherUpdateRequestListener} To be notified once the active weather + * service provider has finished + * processing your request + */ + public void requestWeatherUpdate(@NonNull WeatherLocation weatherLocation, + @NonNull WeatherUpdateRequestListener listener) { + if (sWeatherManagerService == null) { + return; + } + + try { + RequestInfo info = new RequestInfo + .Builder(mRequestInfoListener) + .setWeatherLocation(weatherLocation) + .build(); + if (listener != null) mWeatherUpdateRequestListeners.put(info, listener); + sWeatherManagerService.updateWeather(info); + } catch (RemoteException e) { + } + } + + /** + * Request the active weather provider service to lookup the supplied city name. + * + * @param city The city name + * @param listener {@link LookupCityRequestListener} To be notified once the request has been + * completed. Upon success, a list of + * {@link cyanogenmod.weather.WeatherLocation} + * will be provided + */ + public void lookupCity(@NonNull String city, @NonNull LookupCityRequestListener listener) { + if (sWeatherManagerService == null) { + return; + } + try { + RequestInfo info = new RequestInfo + .Builder(mRequestInfoListener) + .setCityName(city) + .build(); + if (listener != null) mLookupNameRequestListeners.put(info, listener); + sWeatherManagerService.lookupCity(info); + } catch (RemoteException e) { + } + } + + /** + * Registers a {@link WeatherServiceProviderChangeListener} to be notified when a new weather + * service provider becomes active. + * @param listener {@link WeatherServiceProviderChangeListener} to register + */ + public void registerWeatherServiceProviderChangeListener( + @NonNull WeatherServiceProviderChangeListener listener) { + synchronized (mProviderChangedListeners) { + if (mProviderChangedListeners.contains(listener)) { + throw new IllegalArgumentException("Listener already registered"); + } + if (mProviderChangedListeners.size() == 0) { + try { + sWeatherManagerService.registerWeatherServiceProviderChangeListener( + mProviderChangeListener); + } catch (RemoteException e){ + } + } + mProviderChangedListeners.add(listener); + } + } + + /** + * Unregisters a listener + * @param listener A previously registered {@link WeatherServiceProviderChangeListener} + */ + public void unregisterWeatherServiceProviderChangeListener( + @NonNull WeatherServiceProviderChangeListener listener) { + synchronized (mProviderChangedListeners) { + if (!mProviderChangedListeners.contains(listener)) { + throw new IllegalArgumentException("Listener was never registered"); + } + mProviderChangedListeners.remove(listener); + if (mProviderChangedListeners.size() == 0) { + try { + sWeatherManagerService.unregisterWeatherServiceProviderChangeListener( + mProviderChangeListener); + } catch(RemoteException e){ + } + } + } + } + + /** + * Gets the service's label as declared by the active weather service provider in its manifest + * @return the service's label + */ + public String getActiveWeatherServiceProviderLabel() { + try { + return sWeatherManagerService.getActiveWeatherServiceProviderLabel(); + } catch(RemoteException e){ + } + return null; + } + + private final IWeatherServiceProviderChangeListener mProviderChangeListener = + new IWeatherServiceProviderChangeListener.Stub() { + @Override + public void onWeatherServiceProviderChanged(final String providerName) { + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (mProviderChangedListeners) { + List<WeatherServiceProviderChangeListener> deadListeners + = new ArrayList<>(); + for (WeatherServiceProviderChangeListener listener + : mProviderChangedListeners) { + try { + listener.onWeatherServiceProviderChanged(providerName); + } catch (Throwable e) { + deadListeners.add(listener); + } + } + if (deadListeners.size() > 0) { + for (WeatherServiceProviderChangeListener listener : deadListeners) { + mProviderChangedListeners.remove(listener); + } + } + } + } + }); + } + }; + + private final IRequestInfoListener mRequestInfoListener = new IRequestInfoListener.Stub() { + + @Override + public void onWeatherRequestCompleted(final RequestInfo requestInfo, final int state, + final WeatherInfo weatherInfo) { + final WeatherUpdateRequestListener listener + = mWeatherUpdateRequestListeners.remove(requestInfo); + if (listener != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + listener.onWeatherRequestCompleted(state, weatherInfo); + } + }); + } + } + + @Override + public void onLookupCityRequestCompleted(RequestInfo requestInfo, + final List<WeatherLocation> weatherLocations) { + + final LookupCityRequestListener listener + = mLookupNameRequestListeners.remove(requestInfo); + if (listener != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + ArrayList<WeatherLocation> list = null; + if (weatherLocations != null) { + list = new ArrayList<>(weatherLocations); + } + listener.onLookupCityRequestCompleted(list); + } + }); + } + } + }; + + /** + * Interface used to receive notifications upon completion of a weather update request + */ + public interface WeatherUpdateRequestListener { + /** + * This method will be called when the weather service provider has finished processing the + * request + * + * @param state Any of the following values + * {@link #WEATHER_REQUEST_COMPLETED} + * {@link #WEATHER_REQUEST_ALREADY_IN_PROGRESS} + * {@link #WEATHER_REQUEST_SUBMITTED_TOO_SOON} + * {@link #WEATHER_REQUEST_FAILED} + * + * @param weatherInfo A fully populated {@link WeatherInfo} if state is + * {@link #WEATHER_REQUEST_COMPLETED}, null otherwise + */ + void onWeatherRequestCompleted(int state, WeatherInfo weatherInfo); + } + + /** + * Interface used to receive notifications upon completion of a request to lookup a city name + */ + public interface LookupCityRequestListener { + /** + * This method will be called when the weather service provider has finished processing the + * request. The argument can be null if the provider couldn't find a match + * + * @param locations + */ + void onLookupCityRequestCompleted(ArrayList<WeatherLocation> locations); + } + + /** + * Interface used to be notified when the user changes the weather service provider + */ + public interface WeatherServiceProviderChangeListener { + /** + * This method will be called when a new weather service provider becomes active in the + * system. The parameter can be null when + * <p>The user removed the active weather service provider from the system </p> + * <p>The active weather provider was disabled.</p> + * + * @param providerLabel The label as declared on the weather service provider manifest + */ + void onWeatherServiceProviderChanged(String providerLabel); + } +} diff --git a/sdk/src/java/cyanogenmod/weather/ICMWeatherManager.aidl b/sdk/src/java/cyanogenmod/weather/ICMWeatherManager.aidl new file mode 100644 index 0000000..21e1d26 --- /dev/null +++ b/sdk/src/java/cyanogenmod/weather/ICMWeatherManager.aidl @@ -0,0 +1,31 @@ +/* + * 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.weather; + +import cyanogenmod.weather.IWeatherServiceProviderChangeListener; +import cyanogenmod.weather.RequestInfo; + +/** @hide */ +interface ICMWeatherManager { + oneway void updateWeather(in RequestInfo info); + oneway void lookupCity(in RequestInfo info); + oneway void registerWeatherServiceProviderChangeListener( + in IWeatherServiceProviderChangeListener listener); + oneway void unregisterWeatherServiceProviderChangeListener( + in IWeatherServiceProviderChangeListener listener); + String getActiveWeatherServiceProviderLabel(); +}
\ No newline at end of file diff --git a/sdk/src/java/cyanogenmod/weather/IRequestInfoListener.aidl b/sdk/src/java/cyanogenmod/weather/IRequestInfoListener.aidl new file mode 100644 index 0000000..553da71 --- /dev/null +++ b/sdk/src/java/cyanogenmod/weather/IRequestInfoListener.aidl @@ -0,0 +1,31 @@ +/* + * 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.weather; + +import cyanogenmod.weather.RequestInfo; +import cyanogenmod.weather.WeatherInfo; +import cyanogenmod.weather.WeatherLocation; + +import java.util.List; + +/** @hide */ +oneway interface IRequestInfoListener { + void onWeatherRequestCompleted(in RequestInfo requestInfo, int state, + in WeatherInfo weatherInfo); + void onLookupCityRequestCompleted(in RequestInfo requestInfo, + in List<WeatherLocation> weatherLocation); +}
\ No newline at end of file diff --git a/sdk/src/java/cyanogenmod/weather/IWeatherServiceProviderChangeListener.aidl b/sdk/src/java/cyanogenmod/weather/IWeatherServiceProviderChangeListener.aidl new file mode 100644 index 0000000..7c823b6 --- /dev/null +++ b/sdk/src/java/cyanogenmod/weather/IWeatherServiceProviderChangeListener.aidl @@ -0,0 +1,22 @@ +/* + * 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.weather; + +/** @hide */ +oneway interface IWeatherServiceProviderChangeListener { + void onWeatherServiceProviderChanged(String providerLabel); +}
\ No newline at end of file diff --git a/sdk/src/java/cyanogenmod/weather/RequestInfo.aidl b/sdk/src/java/cyanogenmod/weather/RequestInfo.aidl new file mode 100644 index 0000000..5e53b93 --- /dev/null +++ b/sdk/src/java/cyanogenmod/weather/RequestInfo.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.weather; + +parcelable RequestInfo;
\ No newline at end of file diff --git a/sdk/src/java/cyanogenmod/weather/RequestInfo.java b/sdk/src/java/cyanogenmod/weather/RequestInfo.java new file mode 100644 index 0000000..353b5fd --- /dev/null +++ b/sdk/src/java/cyanogenmod/weather/RequestInfo.java @@ -0,0 +1,356 @@ +/* + * 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.weather; + +import android.location.Location; +import android.os.Parcel; +import android.os.Parcelable; + +import cyanogenmod.os.Build; +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; +import cyanogenmod.providers.WeatherContract; + +/** + * This class holds the information of a request submitted to the active weather provider service + */ +public final class RequestInfo implements Parcelable { + + private Location mLocation; + private String mCityName; + private WeatherLocation mWeatherLocation; + private int mRequestType; + private IRequestInfoListener mListener; + private int mTempUnit; + private int mKey; + private boolean mIsQueryOnly; + + /** + * A request to update the weather data using a geographical {@link android.location.Location} + */ + public static final int TYPE_GEO_LOCATION_REQ = 1; + /** + * A request to update the weather data using a {@link WeatherLocation} + */ + public static final int TYPE_WEATHER_LOCATION_REQ = 2; + + /** + * A request to look up a city name + */ + public static final int TYPE_LOOKUP_CITY_NAME_REQ = 3; + + private RequestInfo() {} + + /* package */ static class Builder { + private Location mLocation; + private String mCityName; + private WeatherLocation mWeatherLocation; + private int mRequestType; + private IRequestInfoListener mListener; + private int mTempUnit = WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT; + private boolean mIsQueryOnly = false; + + public Builder(IRequestInfoListener listener) { + this.mListener = listener; + } + + /** + * Sets the city name and identifies this request as a {@link #TYPE_LOOKUP_CITY_NAME_REQ} + * request. If set, will null out the location and weather location. + */ + public Builder setCityName(String cityName) { + this.mCityName = cityName; + this.mRequestType = TYPE_LOOKUP_CITY_NAME_REQ; + this.mLocation = null; + this.mWeatherLocation = null; + return this; + } + + /** + * Sets the Location and identifies this request as a {@link #TYPE_GEO_LOCATION_REQ}. If + * set, will null out the city name and weather location. + */ + public Builder setLocation(Location location) { + this.mLocation = location; + this.mCityName = null; + this.mWeatherLocation = null; + this.mRequestType = TYPE_GEO_LOCATION_REQ; + return this; + } + + /** + * Sets the weather location and identifies this request as a + * {@link #TYPE_WEATHER_LOCATION_REQ}. If set, will null out the location and city name + */ + public Builder setWeatherLocation(WeatherLocation weatherLocation) { + this.mWeatherLocation = weatherLocation; + this.mLocation = null; + this.mCityName = null; + this.mRequestType = TYPE_WEATHER_LOCATION_REQ; + return this; + } + + /** + * Sets the unit in which the temperature will be reported if the request is honored. + * Valid values are: + * <ul> + * {@link cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit#CELSIUS} + * {@link cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit#FAHRENHEIT} + * </ul> + * Any other value will generate an IllegalArgumentException. If the temperature unit is not + * set, the default will be degrees Fahrenheit + * @param unit A valid temperature unit + */ + public Builder setTemperatureUnit(int unit) { + if (!isValidTempUnit(unit)) { + throw new IllegalArgumentException("Invalid temperature unit"); + } + this.mTempUnit = unit; + return this; + } + + /** + * If this is a weather request, marks the request as a query only, meaning that the + * content provider won't be updated after the active weather service has finished + * processing the request. + */ + public Builder queryOnly() { + switch (mRequestType) { + case TYPE_GEO_LOCATION_REQ: + case TYPE_WEATHER_LOCATION_REQ: + this.mIsQueryOnly = true; + break; + default: + this.mIsQueryOnly = false; + break; + } + return this; + } + + public RequestInfo build() { + RequestInfo info = new RequestInfo(); + info.mListener = this.mListener; + info.mRequestType = this.mRequestType; + info.mCityName = this.mCityName; + info.mWeatherLocation = this.mWeatherLocation; + info.mLocation = this.mLocation; + info.mTempUnit = this.mTempUnit; + info.mIsQueryOnly = this.mIsQueryOnly; + info.mKey = this.hashCode(); + return info; + } + + private boolean isValidTempUnit(int unit) { + switch (unit) { + case WeatherContract.WeatherColumns.TempUnit.CELSIUS: + case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT: + return true; + default: + return false; + } + } + + } + + private RequestInfo(Parcel parcel) { + // Read parcelable version via the Concierge + ParcelInfo parcelInfo = Concierge.receiveParcel(parcel); + int parcelableVersion = parcelInfo.getParcelVersion(); + + if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) { + mKey = parcel.readInt(); + mRequestType = parcel.readInt(); + switch (mRequestType) { + case TYPE_GEO_LOCATION_REQ: + mLocation = Location.CREATOR.createFromParcel(parcel); + mTempUnit = parcel.readInt(); + break; + case TYPE_WEATHER_LOCATION_REQ: + mWeatherLocation = WeatherLocation.CREATOR.createFromParcel(parcel); + mTempUnit = parcel.readInt(); + break; + case TYPE_LOOKUP_CITY_NAME_REQ: + mCityName = parcel.readString(); + break; + } + mIsQueryOnly = (parcel.readInt() == 1); + mListener = IRequestInfoListener.Stub.asInterface(parcel.readStrongBinder()); + } + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + + /** + * @return The request type + */ + public int getRequestType() { + return mRequestType; + } + + /** + * @return the {@link android.location.Location} if this is a request by location, null + * otherwise + */ + public Location getLocation() { + return mLocation; + } + + /** + * @return the {@link cyanogenmod.weather.WeatherLocation} if this is a request by weather + * location, null otherwise + */ + public WeatherLocation getWeatherLocation() { + return mWeatherLocation; + } + + /** + * @hide + */ + public IRequestInfoListener getRequestListener() { + return mListener; + } + + /** + * @return the city name if this is a lookup request, null otherwise + */ + public String getCityName() { + return mCityName; + } + + /** + * @return the temperature unit if this is a weather request, -1 otherwise + */ + public int getTemperatureUnit() { + switch (mRequestType) { + case TYPE_GEO_LOCATION_REQ: + case TYPE_WEATHER_LOCATION_REQ: + return mTempUnit; + default: + return -1; + } + } + + /** + * @return if this is a weather request, whether the request will update the content provider. + * False for other kind of requests + * @hide + */ + public boolean isQueryOnlyWeatherRequest() { + switch (mRequestType) { + case TYPE_GEO_LOCATION_REQ: + case TYPE_WEATHER_LOCATION_REQ: + return mIsQueryOnly; + default: + return false; + } + } + + public static final Creator<RequestInfo> CREATOR = new Creator<RequestInfo>() { + @Override + public RequestInfo createFromParcel(Parcel in) { + return new RequestInfo(in); + } + + @Override + public RequestInfo[] newArray(int size) { + return new RequestInfo[size]; + } + }; + + @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); + + // ==== ELDERBERRY ===== + dest.writeInt(mKey); + dest.writeInt(mRequestType); + switch (mRequestType) { + case TYPE_GEO_LOCATION_REQ: + mLocation.writeToParcel(dest, 0); + dest.writeInt(mTempUnit); + break; + case TYPE_WEATHER_LOCATION_REQ: + mWeatherLocation.writeToParcel(dest, 0); + dest.writeInt(mTempUnit); + break; + case TYPE_LOOKUP_CITY_NAME_REQ: + dest.writeString(mCityName); + break; + } + dest.writeInt(mIsQueryOnly == true ? 1 : 0); + dest.writeStrongBinder(mListener.asBinder()); + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("{ Request for "); + switch (mRequestType) { + case TYPE_GEO_LOCATION_REQ: + builder.append("Location: ").append(mLocation); + builder.append(" Temp Unit: "); + if (mTempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) { + builder.append("Fahrenheit"); + } else { + builder.append(" Celsius"); + } + break; + case TYPE_WEATHER_LOCATION_REQ: + builder.append("WeatherLocation: ").append(mWeatherLocation); + builder.append(" Temp Unit: "); + if (mTempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) { + builder.append("Fahrenheit"); + } else { + builder.append(" Celsius"); + } + break; + case TYPE_LOOKUP_CITY_NAME_REQ: + builder.append("Lookup City: ").append(mCityName); + break; + } + return builder.append(" }").toString(); + } + + @Override + public int hashCode() { + //The hashcode of this object was stored when it was built. This is an + //immutable object but we need to preserve the hashcode since this object is parcelable and + //it's reconstructed over IPC, and clients of this object might want to store it in a + //collection that relies on this code to identify the object + return mKey; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof RequestInfo) { + RequestInfo info = (RequestInfo) obj; + return (info.hashCode() == this.mKey); + } + return false; + } +} diff --git a/sdk/src/java/cyanogenmod/weather/WeatherInfo.aidl b/sdk/src/java/cyanogenmod/weather/WeatherInfo.aidl new file mode 100644 index 0000000..ffff02c --- /dev/null +++ b/sdk/src/java/cyanogenmod/weather/WeatherInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2016 The CyanongenMod 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.weather; + +parcelable WeatherInfo;
\ No newline at end of file diff --git a/sdk/src/java/cyanogenmod/weather/WeatherInfo.java b/sdk/src/java/cyanogenmod/weather/WeatherInfo.java new file mode 100755 index 0000000..7d8686e --- /dev/null +++ b/sdk/src/java/cyanogenmod/weather/WeatherInfo.java @@ -0,0 +1,524 @@ +/* + * Copyright (C) 2016 The CyanongenMod 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.weather; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import cyanogenmod.os.Build; +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; +import cyanogenmod.providers.WeatherContract; +import cyanogenmod.weatherservice.ServiceRequest; +import cyanogenmod.weatherservice.ServiceRequestResult; + +import java.util.ArrayList; + +/** + * This class represents the weather information that a + * {@link cyanogenmod.weatherservice.WeatherProviderService} will use to update the weather content + * provider. A weather provider service will be called by the system to process an update + * request at any time. If the service successfully processes the request, then the weather provider + * service is responsible of calling + * {@link ServiceRequest#complete(ServiceRequestResult)} to notify the + * system that the request was completed and that the weather content provider should be updated + * with the supplied weather information. + */ +public final class WeatherInfo implements Parcelable { + + private String mCityId; + private String mCity; + private int mConditionCode; + private float mTemperature; + private int mTempUnit; + private float mHumidity; + private float mWindSpeed; + private float mWindDirection; + private int mWindSpeedUnit; + private long mTimestamp; + private ArrayList<DayForecast> mForecastList; + int mKey; + + private WeatherInfo() {} + + public static class Builder { + private String mCityId; + private String mCity; + private int mConditionCode; + private float mTemperature; + private int mTempUnit; + private float mHumidity; + private float mWindSpeed; + private float mWindDirection; + private int mWindSpeedUnit; + private long mTimestamp; + private ArrayList<DayForecast> mForecastList; + + public Builder(long timestamp) { + mTimestamp = timestamp; + } + + public Builder setCity(String cityId, @NonNull String cityName) { + if (cityName == null || cityId == null) { + throw new IllegalArgumentException("City name and id can't be null"); + } + mCityId = cityId; + mCity = cityName; + return this; + } + + public Builder setTemperature(float temperature, int tempUnit) { + if (!isValidTempUnit(tempUnit)) { + throw new IllegalArgumentException("Invalid temperature unit"); + } + + if (Float.isNaN(temperature)) { + throw new IllegalArgumentException("Invalid temperature value"); + } + + mTemperature = temperature; + mTempUnit = tempUnit; + return this; + } + + public Builder setHumidity(float humidity) { + if (Float.isNaN(humidity)) { + throw new IllegalArgumentException("Invalid humidity value"); + } + + mHumidity = humidity; + return this; + } + + public Builder setWind(float windSpeed, float windDirection, int windSpeedUnit) { + if (Float.isNaN(windSpeed)) { + throw new IllegalArgumentException("Invalid wind speed value"); + } + if (Float.isNaN(windDirection)) { + throw new IllegalArgumentException("Invalid wind direction value"); + } + if (!isValidWindSpeedUnit(windSpeedUnit)) { + throw new IllegalArgumentException("Invalid speed unit"); + } + mWindSpeed = windSpeed; + mWindSpeedUnit = windSpeedUnit; + mWindDirection = windDirection; + return this; + } + + public Builder setWeatherCondition(int conditionCode) { + if (!isValidWeatherCode(conditionCode)) { + throw new IllegalArgumentException("Invalid weather condition code"); + } + mConditionCode = conditionCode; + return this; + } + + public Builder setForecast(@NonNull ArrayList<DayForecast> forecasts) { + if (forecasts == null) { + throw new IllegalArgumentException("Forecast list can't be null"); + } + mForecastList = forecasts; + return this; + } + + public WeatherInfo build() { + WeatherInfo info = new WeatherInfo(); + info.mCityId = this.mCityId; + info.mCity = this.mCity; + info.mConditionCode = this.mConditionCode; + info.mTemperature = this.mTemperature; + info.mTempUnit = this.mTempUnit; + info.mHumidity = this.mHumidity; + info.mWindSpeed = this.mWindSpeed; + info.mWindDirection = this.mWindDirection; + info.mWindSpeedUnit = this.mWindSpeedUnit; + info.mTimestamp = this.mTimestamp; + info.mForecastList = this.mForecastList; + info.mKey = this.hashCode(); + return info; + } + + private boolean isValidTempUnit(int unit) { + switch (unit) { + case WeatherContract.WeatherColumns.TempUnit.CELSIUS: + case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT: + return true; + default: + return false; + } + } + + private boolean isValidWindSpeedUnit(int unit) { + switch (unit) { + case WeatherContract.WeatherColumns.WindSpeedUnit.KPH: + case WeatherContract.WeatherColumns.WindSpeedUnit.MPH: + return true; + default: + return false; + } + } + } + + + private static boolean isValidWeatherCode(int code) { + if (code < WeatherContract.WeatherColumns.WeatherCode.WEATHER_CODE_MIN + || code > WeatherContract.WeatherColumns.WeatherCode.WEATHER_CODE_MAX) { + if (code != WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE) { + return false; + } + } + return true; + } + + /** + * @return city id + */ + public String getCityId() { + return mCityId; + } + + /** + * @return city name + */ + public String getCity() { + return mCity; + } + + /** + * @return An implementation specific weather condition code + */ + public int getConditionCode() { + return mConditionCode; + } + + /** + * @return humidity + */ + public float getHumidity() { + return mHumidity; + } + + /** + * @return time stamp when the request was processed + */ + public long getTimestamp() { + return mTimestamp; + } + + /** + * @return wind direction (degrees) + */ + public float getWindDirection() { + return mWindDirection; + } + + /** + * @return wind speed + */ + public float getWindSpeed() { + return mWindSpeed; + } + + /** + * @return wind speed unit + */ + public int getWindSpeedUnit() { + return mWindSpeedUnit; + } + + /** + * @return current temperature + */ + public float getTemperature() { + return mTemperature; + } + + /** + * @return temperature unit + */ + public int getTemperatureUnit() { + return mTempUnit; + } + + /** + * @return List of {@link cyanogenmod.weather.WeatherInfo.DayForecast} + */ + public ArrayList<DayForecast> getForecasts() { + return mForecastList; + } + + private WeatherInfo(Parcel parcel) { + // Read parcelable version via the Concierge + ParcelInfo parcelInfo = Concierge.receiveParcel(parcel); + int parcelableVersion = parcelInfo.getParcelVersion(); + + if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) { + mKey = parcel.readInt(); + mCityId = parcel.readString(); + mCity = parcel.readString(); + mConditionCode = parcel.readInt(); + mTemperature = parcel.readFloat(); + mTempUnit = parcel.readInt(); + mHumidity = parcel.readFloat(); + mWindSpeed = parcel.readFloat(); + mWindDirection = parcel.readFloat(); + mWindSpeedUnit = parcel.readInt(); + mTimestamp = parcel.readLong(); + int forecastListSize = parcel.readInt(); + mForecastList = new ArrayList<>(); + while (forecastListSize > 0) { + mForecastList.add(DayForecast.CREATOR.createFromParcel(parcel)); + forecastListSize--; + } + } + + // 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); + + // ==== ELDERBERRY ===== + dest.writeInt(mKey); + dest.writeString(mCityId); + dest.writeString(mCity); + dest.writeInt(mConditionCode); + dest.writeFloat(mTemperature); + dest.writeInt(mTempUnit); + dest.writeFloat(mHumidity); + dest.writeFloat(mWindSpeed); + dest.writeFloat(mWindDirection); + dest.writeInt(mWindSpeedUnit); + dest.writeLong(mTimestamp); + dest.writeInt(mForecastList.size()); + for (DayForecast dayForecast : mForecastList) { + dayForecast.writeToParcel(dest, 0); + } + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + public static final Parcelable.Creator<WeatherInfo> CREATOR = + new Parcelable.Creator<WeatherInfo>() { + + @Override + public WeatherInfo createFromParcel(Parcel source) { + return new WeatherInfo(source); + } + + @Override + public WeatherInfo[] newArray(int size) { + return new WeatherInfo[size]; + } + }; + + /** + * This class represents the weather forecast for a given day + */ + public static class DayForecast implements Parcelable{ + float mLow; + float mHigh; + int mConditionCode; + int mKey; + + private DayForecast() {} + + public static class Builder { + float mLow; + float mHigh; + int mConditionCode; + + public Builder() {} + public Builder setHigh(float high) { + if (Float.isNaN(high)) { + throw new IllegalArgumentException("Invalid high forecast temperature"); + } + mHigh = high; + return this; + } + public Builder setLow(float low) { + if (Float.isNaN(low)) { + throw new IllegalArgumentException("Invalid low forecast temperature"); + } + mLow = low; + return this; + } + + public Builder setWeatherCondition(int code) { + if (!isValidWeatherCode(code)) { + throw new IllegalArgumentException("Invalid weather condition code"); + } + mConditionCode = code; + return this; + } + + public DayForecast build() { + DayForecast forecast = new DayForecast(); + forecast.mLow = this.mLow; + forecast.mHigh = this.mHigh; + forecast.mConditionCode = this.mConditionCode; + forecast.mKey = this.hashCode(); + return forecast; + } + } + + /** + * @return forecasted low temperature + */ + public float getLow() { + return mLow; + } + + /** + * @return not what you think. Returns the forecasted high temperature + */ + public float getHigh() { + return mHigh; + } + + /** + * @return forecasted weather condition code. Implementation specific + */ + public int getConditionCode() { + return mConditionCode; + } + + @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); + + // ==== ELDERBERRY ===== + dest.writeInt(mKey); + dest.writeFloat(mLow); + dest.writeFloat(mHigh); + dest.writeInt(mConditionCode); + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + public static final Parcelable.Creator<DayForecast> CREATOR = + new Parcelable.Creator<DayForecast>() { + @Override + public DayForecast createFromParcel(Parcel source) { + return new DayForecast(source); + } + + @Override + public DayForecast[] newArray(int size) { + return new DayForecast[size]; + } + }; + + private DayForecast(Parcel parcel) { + // Read parcelable version via the Concierge + ParcelInfo parcelInfo = Concierge.receiveParcel(parcel); + int parcelableVersion = parcelInfo.getParcelVersion(); + + if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) { + mKey = parcel.readInt(); + mLow = parcel.readFloat(); + mHigh = parcel.readFloat(); + mConditionCode = parcel.readInt(); + } + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + @Override + public String toString() { + return new StringBuilder() + .append("{Low temp: ").append(mLow) + .append(" High temp: ").append(mHigh) + .append(" Condition code: ").append(mConditionCode) + .append("}").toString(); + } + + @Override + public int hashCode() { + //The hashcode of this object was stored when it was built. This is an + //immutable object but we need to preserve the hashcode since this object is parcelable + //and it's reconstructed over IPC, and clients of this object might want to store it in + //a collection that relies on this code to identify the object + return mKey; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof DayForecast) { + DayForecast forecast = (DayForecast) obj; + return (forecast.hashCode() == this.mKey); + } + return false; + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder() + .append("{CityId: ").append(mCityId) + .append(" City Name: ").append(mCity) + .append(" Condition Code: ").append(mConditionCode) + .append(" Temperature: ").append(mTemperature) + .append(" Temperature Unit: ").append(mTempUnit) + .append(" Humidity: ").append(mHumidity) + .append(" Wind speed: ").append(mWindSpeed) + .append(" Wind direction: ").append(mWindDirection) + .append(" Wind Speed Unit: ").append(mWindSpeedUnit) + .append(" Timestamp: ").append(mTimestamp).append(" Forecasts: ["); + for (DayForecast dayForecast : mForecastList) { + builder.append(dayForecast.toString()); + } + return builder.append("]}").toString(); + } + + @Override + public int hashCode() { + //The hashcode of this object was stored when it was built. This is an + //immutable object but we need to preserve the hashcode since this object is parcelable and + //it's reconstructed over IPC, and clients of this object might want to store it in a + //collection that relies on this code to identify the object + return mKey; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof WeatherInfo) { + WeatherInfo info = (WeatherInfo) obj; + return (info.hashCode() == this.mKey); + } + return false; + } +}
\ No newline at end of file diff --git a/sdk/src/java/cyanogenmod/weather/WeatherLocation.aidl b/sdk/src/java/cyanogenmod/weather/WeatherLocation.aidl new file mode 100644 index 0000000..23f7b24 --- /dev/null +++ b/sdk/src/java/cyanogenmod/weather/WeatherLocation.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.weather; + +parcelable WeatherLocation;
\ No newline at end of file diff --git a/sdk/src/java/cyanogenmod/weather/WeatherLocation.java b/sdk/src/java/cyanogenmod/weather/WeatherLocation.java new file mode 100644 index 0000000..3afcd8e --- /dev/null +++ b/sdk/src/java/cyanogenmod/weather/WeatherLocation.java @@ -0,0 +1,173 @@ +/* + * 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.weather; + +import android.os.Parcel; +import android.os.Parcelable; + +import cyanogenmod.os.Build; +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; + +/** + * A class representing a geographical location that a weather service provider can use to + * get weather data from. Each service provider will potentially populate objects of this class + * with different content, so make sure you don't preserve the values when a service provider + * is changed + */ +public final class WeatherLocation implements Parcelable{ + private String mCityId; + private String mCity; + private String mPostal; + private String mCountryId; + private String mCountry; + private int mKey; + + private WeatherLocation() {} + + public static class Builder { + String mCityId; + String mCity; + String mPostal; + String mCountryId; + String mCountry; + + public Builder(String cityId, String cityName) { + this.mCityId = cityId; + this.mCity = cityName; + } + + public Builder setCountry(String countyId, String country) { + this.mCountryId = countyId; + this.mCountry = country; + return this; + } + + public Builder setPostalCode(String postalCode) { + this.mPostal = postalCode; + return this; + } + + public WeatherLocation build() { + WeatherLocation weatherLocation = new WeatherLocation(); + weatherLocation.mCityId = this.mCityId; + weatherLocation.mCity = this.mCity; + weatherLocation.mPostal = this.mPostal; + weatherLocation.mCountryId = this.mCountryId; + weatherLocation.mCountry = this.mCountry; + weatherLocation.mKey = this.hashCode(); + return weatherLocation; + } + } + + public String getCityId() { + return mCityId; + } + + public String getCity() { + return mCity; + } + + public String getPostalCode() { + return mPostal; + } + + public String getCountryId() { + return mCountryId; + } + + public String getCountry() { + return mCountry; + } + + private WeatherLocation(Parcel in) { + // Read parcelable version via the Concierge + ParcelInfo parcelInfo = Concierge.receiveParcel(in); + int parcelableVersion = parcelInfo.getParcelVersion(); + + if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) { + mKey = in.readInt(); + mCityId = in.readString(); + mCity = in.readString(); + mPostal = in.readString(); + mCountryId = in.readString(); + mCountry = in.readString(); + } + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + public static final Creator<WeatherLocation> CREATOR = new Creator<WeatherLocation>() { + @Override + public WeatherLocation createFromParcel(Parcel in) { + return new WeatherLocation(in); + } + + @Override + public WeatherLocation[] newArray(int size) { + return new WeatherLocation[size]; + } + }; + + @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); + + // ==== ELDERBERRY ===== + dest.writeInt(mKey); + dest.writeString(mCityId); + dest.writeString(mCity); + dest.writeString(mPostal); + dest.writeString(mCountryId); + dest.writeString(mCountry); + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + @Override + public String toString() { + return new StringBuilder() + .append("{ City ID: ").append(mCityId) + .append(" City: ").append(mCity) + .append(" Postal Code: ").append(mPostal) + .append(" Country Id: ").append(mCountryId) + .append(" Country: ").append(mCountry).append("}") + .toString(); + } + + @Override + public int hashCode() { + return mKey; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof WeatherLocation) { + WeatherLocation info = (WeatherLocation) obj; + return (info.hashCode() == this.mKey); + } + return false; + } +} diff --git a/sdk/src/java/cyanogenmod/weather/util/WeatherUtils.java b/sdk/src/java/cyanogenmod/weather/util/WeatherUtils.java new file mode 100644 index 0000000..c89213b --- /dev/null +++ b/sdk/src/java/cyanogenmod/weather/util/WeatherUtils.java @@ -0,0 +1,84 @@ +/* + * 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.weather.util; + + +import cyanogenmod.providers.WeatherContract; + +import java.text.DecimalFormat; + +/** + * Helper class to perform operations and formatting of weather data + */ +public class WeatherUtils { + + /** + * Converts a temperature expressed in degrees Celsius to degrees Fahrenheit + * @param celsius temperature in Celsius + * @return the temperature in degrees Fahrenheit + */ + public static float celsiusToFahrenheit(float celsius) { + return ((celsius * (9f/5f)) + 32f); + } + + /** + * Converts a temperature expressed in degrees Fahrenheit to degrees Celsius + * @param fahrenheit temperature in Fahrenheit + * @return the temperature in degrees Celsius + */ + public static float fahrenheitToCelsius(float fahrenheit) { + return ((fahrenheit - 32f) * (5f/9f)); + } + + /** + * Returns a string representation of the temperature and unit supplied. The temperature value + * will be half-even rounded. + * @param temperature the temperature value + * @param tempUnit A valid {@link WeatherContract.WeatherColumns.TempUnit} + * @return A string with the format XX°F or XX°C (where XX is the temperature) + * depending on the temperature unit that was provided or null if an invalid unit is supplied + */ + public static String formatTemperature(float temperature, int tempUnit) { + if (!isValidTempUnit(tempUnit)) return null; + if (Float.isNaN(temperature)) return "-"; + + DecimalFormat noDigitsFormat = new DecimalFormat("0"); + String noDigitsTemp = noDigitsFormat.format(temperature); + if (noDigitsTemp.equals("-0")) { + noDigitsTemp = "0"; + } + + StringBuilder formatted = new StringBuilder() + .append(noDigitsTemp).append("\u00b0"); + if (tempUnit == WeatherContract.WeatherColumns.TempUnit.CELSIUS) { + formatted.append("C"); + } else if (tempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) { + formatted.append("F"); + } + return formatted.toString(); + } + + private static boolean isValidTempUnit(int unit) { + switch (unit) { + case WeatherContract.WeatherColumns.TempUnit.CELSIUS: + case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT: + return true; + default: + return false; + } + } +} diff --git a/sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderService.aidl b/sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderService.aidl new file mode 100644 index 0000000..d9eceb3 --- /dev/null +++ b/sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderService.aidl @@ -0,0 +1,28 @@ +/* + * 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.weatherservice; + +import cyanogenmod.weatherservice.IWeatherProviderServiceClient; +import cyanogenmod.weather.RequestInfo; + +/** @hide */ +oneway interface IWeatherProviderService { + void processWeatherUpdateRequest(in RequestInfo request); + void processCityNameLookupRequest(in RequestInfo request); + void setServiceClient(in IWeatherProviderServiceClient client); + void cancelOngoingRequests(); +}
\ No newline at end of file diff --git a/sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderServiceClient.aidl b/sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderServiceClient.aidl new file mode 100644 index 0000000..a4baa4c --- /dev/null +++ b/sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderServiceClient.aidl @@ -0,0 +1,26 @@ +/* + * 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.weatherservice; + +import cyanogenmod.weather.RequestInfo; +import cyanogenmod.weatherservice.ServiceRequestResult; + +/** @hide */ +oneway interface IWeatherProviderServiceClient { + void setServiceRequestState(in RequestInfo requestInfo, in ServiceRequestResult result, + int state); +}
\ No newline at end of file diff --git a/sdk/src/java/cyanogenmod/weatherservice/ServiceRequest.java b/sdk/src/java/cyanogenmod/weatherservice/ServiceRequest.java new file mode 100644 index 0000000..e43218d --- /dev/null +++ b/sdk/src/java/cyanogenmod/weatherservice/ServiceRequest.java @@ -0,0 +1,118 @@ +/* + * 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.weatherservice; + +import android.annotation.NonNull; +import android.os.RemoteException; +import cyanogenmod.weather.CMWeatherManager; +import cyanogenmod.weather.RequestInfo; + +/** + * This class represents a request submitted by the system to the active weather provider service + */ +public final class ServiceRequest { + + private final RequestInfo mInfo; + private final IWeatherProviderServiceClient mClient; + + /** + * If a request is marked as cancelled, it means the client does not want to know anything about + * this request anymore + */ + private volatile boolean mCancelled; + + /* package */ ServiceRequest(RequestInfo info, IWeatherProviderServiceClient client) { + mInfo = info; + mClient = client; + } + + /** + * Obtains the request information + * @return {@link cyanogenmod.weather.RequestInfo} + */ + public RequestInfo getRequestInfo() { + return mInfo; + } + + /** + * This method should be called once the request has been completed + */ + public void complete(@NonNull ServiceRequestResult result) { + if (!mCancelled) { + try { + final int requestType = mInfo.getRequestType(); + switch (requestType) { + case RequestInfo.TYPE_GEO_LOCATION_REQ: + case RequestInfo.TYPE_WEATHER_LOCATION_REQ: + if (result.getWeatherInfo() == null) { + throw new IllegalStateException("The service request result does not" + + " contain a valid WeatherInfo object"); + } + mClient.setServiceRequestState(mInfo, result, + CMWeatherManager.WEATHER_REQUEST_COMPLETED); + break; + case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ: + if (result.getLocationLookupList() == null) { + //In case the user decided to mark this request as completed with an + //empty list. It's not necessarily a failure + mClient.setServiceRequestState(mInfo, null, + CMWeatherManager.LOOKUP_REQUEST_NO_MATCH_FOUND); + } else { + mClient.setServiceRequestState(mInfo, result, + CMWeatherManager.LOOKUP_REQUEST_COMPLETED); + } + break; + } + } catch (RemoteException e) { + } + } + } + + /** + * This method should be called if the service failed to process the request + * (no internet connection, time out, etc.) + */ + public void fail() { + if (!mCancelled) { + try { + final int requestType = mInfo.getRequestType(); + switch (requestType) { + case RequestInfo.TYPE_GEO_LOCATION_REQ: + case RequestInfo.TYPE_WEATHER_LOCATION_REQ: + mClient.setServiceRequestState(mInfo, null, + CMWeatherManager.WEATHER_REQUEST_FAILED); + break; + case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ: + mClient.setServiceRequestState(mInfo, null, + CMWeatherManager.LOOKUP_REQUEST_FAILED); + break; + } + } catch (RemoteException e) { + } + } + } + + /** + * Called by the WeatherProviderService base class to notify we don't want this request anymore. + * The service implementing the WeatherProviderService will be notified of this action + * via onRequestCancelled() + * @hide + */ + public void cancel() { + mCancelled = true; + } +} diff --git a/sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.aidl b/sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.aidl new file mode 100644 index 0000000..669ece5 --- /dev/null +++ b/sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.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.weatherservice; + +parcelable ServiceRequestResult;
\ No newline at end of file diff --git a/sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.java b/sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.java new file mode 100644 index 0000000..07f4cd9 --- /dev/null +++ b/sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.java @@ -0,0 +1,204 @@ +/* + * 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.weatherservice; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import cyanogenmod.os.Build; +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; +import cyanogenmod.weather.WeatherLocation; +import cyanogenmod.weather.WeatherInfo; + +import java.util.ArrayList; + +/** + * Use this class to build a request result. + */ +public final class ServiceRequestResult implements Parcelable { + + private WeatherInfo mWeatherInfo; + private ArrayList<WeatherLocation> mLocationLookupList; + private int mKey; + + private ServiceRequestResult() {} + + private ServiceRequestResult(Parcel in) { + // Read parcelable version via the Concierge + ParcelInfo parcelInfo = Concierge.receiveParcel(in); + int parcelableVersion = parcelInfo.getParcelVersion(); + + if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) { + mKey = in.readInt(); + int hasWeatherInfo = in.readInt(); + if (hasWeatherInfo == 1) { + mWeatherInfo = WeatherInfo.CREATOR.createFromParcel(in); + } + int hasLocationLookupList = in.readInt(); + if (hasLocationLookupList == 1) { + mLocationLookupList = new ArrayList<>(); + int listSize = in.readInt(); + while (listSize > 0) { + mLocationLookupList.add(WeatherLocation.CREATOR.createFromParcel(in)); + listSize--; + } + } + } + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + public static final Creator<ServiceRequestResult> CREATOR + = new Creator<ServiceRequestResult>() { + @Override + public ServiceRequestResult createFromParcel(Parcel in) { + return new ServiceRequestResult(in); + } + + @Override + public ServiceRequestResult[] newArray(int size) { + return new ServiceRequestResult[size]; + } + }; + + @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); + + // ==== ELDERBERRY ===== + dest.writeInt(mKey); + if (mWeatherInfo != null) { + dest.writeInt(1); + mWeatherInfo.writeToParcel(dest, 0); + } else { + dest.writeInt(0); + } + if (mLocationLookupList != null) { + dest.writeInt(1); + dest.writeInt(mLocationLookupList.size()); + for (WeatherLocation lookup : mLocationLookupList) { + lookup.writeToParcel(dest, 0); + } + } else { + dest.writeInt(0); + } + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + public static class Builder { + private WeatherInfo mBuilderWeatherInfo; + private ArrayList<WeatherLocation> mBuilderLocationLookupList; + public Builder() { + this.mBuilderWeatherInfo = null; + this.mBuilderLocationLookupList = null; + } + + /** + * Add the supplied weather information to the result. Attempting to add a WeatherLocation + * list to the same builder will cause the system to throw IllegalArgumentException + * + * @param weatherInfo The WeatherInfo object holding the data that will be used to update + * the weather content provider + */ + public Builder setWeatherInfo(@NonNull WeatherInfo weatherInfo) { + if (mBuilderLocationLookupList != null) { + throw new IllegalArgumentException("Can't add weather information when you have" + + " already added a WeatherLocation list"); + } + + if (weatherInfo == null) { + throw new IllegalArgumentException("WeatherInfo can't be null"); + } + + mBuilderWeatherInfo = weatherInfo; + return this; + } + + /** + * Add the supplied list of WeatherLocation objects to the result. Attempting to add a + * WeatherInfo object to the same builder will cause the system to throw + * IllegalArgumentException + * + * @param locations The list of WeatherLocation objects. The list should not be null + */ + public Builder setLocationLookupList(@NonNull ArrayList<WeatherLocation> locations) { + if (mBuilderWeatherInfo != null) { + throw new IllegalArgumentException("Can't add a WeatherLocation list when you have" + + " already added weather information"); + } + + mBuilderLocationLookupList = locations; + return this; + } + + /** + * Creates a {@link ServiceRequest} with the arguments + * supplied to this builder + * @return {@link ServiceRequestResult} + */ + public ServiceRequestResult build() { + ServiceRequestResult result = new ServiceRequestResult(); + result.mWeatherInfo = this.mBuilderWeatherInfo; + result.mLocationLookupList = this.mBuilderLocationLookupList; + result.mKey = this.hashCode(); + return result; + } + } + + /** + * @return The WeatherInfo object supplied by the weather provider service + */ + public WeatherInfo getWeatherInfo() { + return mWeatherInfo; + } + + /** + * @return The list of WeatherLocation objects supplied by the weather provider service + */ + public ArrayList<WeatherLocation> getLocationLookupList() { + return mLocationLookupList; + } + + @Override + public int hashCode() { + //The hashcode of this object was stored when it was built. This is an + //immutable object but we need to preserve the hashcode since this object is parcelable and + //it's reconstructed over IPC, and clients of this object might want to store it in a + //collection that relies on this code to identify the object + return mKey; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ServiceRequestResult) { + ServiceRequestResult request = (ServiceRequestResult) obj; + return (request.hashCode() == this.mKey); + } + return false; + } +} diff --git a/sdk/src/java/cyanogenmod/weatherservice/WeatherProviderService.java b/sdk/src/java/cyanogenmod/weatherservice/WeatherProviderService.java new file mode 100644 index 0000000..759d5fc --- /dev/null +++ b/sdk/src/java/cyanogenmod/weatherservice/WeatherProviderService.java @@ -0,0 +1,196 @@ +/* + * 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.weatherservice; + +import android.annotation.SdkConstant; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import cyanogenmod.weather.RequestInfo; + +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * This is the base class for implementing a weather provider service. A weather provider service + * can handle weather update requests and update the weather content provider data by processing + * a {@link ServiceRequest} + * + * A print service is declared as any other service in an AndroidManifest.xml but it must also + * specify that in handles the {@link android.content.Intent} with action + * {@link #SERVICE_INTERFACE cyanogenmod.weatherservice.WeatherProviderService}. Failure to declare + * this intent will cause the system to ignore the weather provider service. Additionally, a + * weather provider service must request the + * {@link cyanogenmod.platform.Manifest.permission#BIND_WEATHER_PROVIDER_SERVICE} permission to + * ensure that only the system can bind to it. Failure to request this permission will cause the + * system to ignore this weather provider service. Following is an example declaration: + * + * <pre> + * <service android:name=".MyWeatherProviderService" + * android:permission="cyanogenmod.permission.BIND_WEATHER_PROVIDER_SERVICE"> + * <intent-filter> + * <action android:name="cyanogenmod.weatherservice.WeatherProviderService" /> + * <intent-filter> + * . . . + * </service> + * </pre> + * + */ +public abstract class WeatherProviderService extends Service { + + private Handler mHandler; + private IWeatherProviderServiceClient mClient; + private Set<ServiceRequest> mWeakRequestsSet + = Collections.newSetFromMap(new WeakHashMap<ServiceRequest, Boolean>()); + + /** + * The {@link android.content.Intent} action that must be declared as handled by a service in + * its manifest for the system to recognize it as a weather provider service + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE + = "cyanogenmod.weatherservice.WeatherProviderService"; + + /** + * Name under which a {@link WeatherProviderService} publishes information about itself. + * This meta-data must reference an XML resource containing + * a <code><weather-provider-service></code> + * tag. + */ + public static final String SERVICE_META_DATA = "cyanogenmod.weatherservice"; + + @Override + protected final void attachBaseContext(Context base) { + super.attachBaseContext(base); + mHandler = new ServiceHandler(base.getMainLooper()); + } + + @Override + public final IBinder onBind(Intent intent) { + return mBinder; + } + + private final IWeatherProviderService.Stub mBinder = new IWeatherProviderService.Stub() { + + @Override + public void processWeatherUpdateRequest(final RequestInfo info) { + mHandler.obtainMessage(ServiceHandler.MSG_ON_NEW_REQUEST, info).sendToTarget(); + } + + @Override + public void processCityNameLookupRequest(final RequestInfo info) { + mHandler.obtainMessage(ServiceHandler.MSG_ON_NEW_REQUEST, info).sendToTarget(); + } + + @Override + public void setServiceClient(IWeatherProviderServiceClient client) { + mHandler.obtainMessage(ServiceHandler.MSG_SET_CLIENT, client).sendToTarget(); + } + + @Override + public void cancelOngoingRequests() { + mHandler.obtainMessage(ServiceHandler.MSG_CANCEL_ONGOING_REQUESTS).sendToTarget(); + } + }; + + private class ServiceHandler extends Handler { + + public ServiceHandler(Looper looper) { + super(looper); + } + public static final int MSG_SET_CLIENT = 1; + public static final int MSG_ON_NEW_REQUEST = 2; + public static final int MSG_CANCEL_ONGOING_REQUESTS = 3; + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SET_CLIENT: { + mClient = (IWeatherProviderServiceClient) msg.obj; + if (mClient != null) { + onConnected(); + } else { + onDisconnected(); + } + return; + } + case MSG_ON_NEW_REQUEST: { + RequestInfo info = (RequestInfo) msg.obj; + if (info != null) { + ServiceRequest request = new ServiceRequest(info, mClient); + synchronized (mWeakRequestsSet) { + mWeakRequestsSet.add(request); + } + onRequestSubmitted(request); + } + return; + } + case MSG_CANCEL_ONGOING_REQUESTS: { + synchronized (mWeakRequestsSet) { + for (final ServiceRequest request : mWeakRequestsSet) { + if (request != null) { + request.cancel(); + mWeakRequestsSet.remove(request); + mHandler.post(new Runnable() { + @Override + public void run() { + onRequestCancelled(request); + } + }); + } + } + } + } + } + } + } + + /** + * The system has connected to this service. + */ + protected void onConnected() { + /* Do nothing */ + } + + /** + * The system has disconnected from this service. + */ + protected void onDisconnected() { + /* Do nothing */ + } + + /** + * A new request has been submitted to this service + * @param request The service request to be processed by this service + */ + protected abstract void onRequestSubmitted(ServiceRequest request); + + /** + * Called when the system is not interested on this request anymore. Note that the service + * <b>has marked the request as cancelled</b> and you must stop any ongoing operation + * (such as pulling data from internet) that this service could've been performing to honor the + * request. + * + * @param request The request cancelled by the system + */ + protected abstract void onRequestCancelled(ServiceRequest request); +}
\ No newline at end of file |