From 0cdb1d513c70a794db0e29696ef620c573aa96ea Mon Sep 17 00:00:00 2001 From: Matt Garnes Date: Tue, 14 Jul 2015 16:29:56 -0700 Subject: [PATCH] Add SettingsManager. Add new APIs for changing a subset of system settings. Protected by cyanogenmod.permission.MODIFY_NETWORK_SETTINGS: - Add ability to toggle airplane mode on/off. - Add ability to toggle mobile data on/off. Protected by android.permission.REBOOT: - Add ability to shutdown or reboot the device. Change-Id: I5e943be11260c58afa664f1702c0ecb4413528fe --- api/cm_current.txt | 9 + .../internal/SettingsManagerService.java | 158 ++++++++++++++++++ cm/res/AndroidManifest.xml | 7 + cm/res/res/values/strings.xml | 3 + .../cyanogenmod/app/CMContextConstants.java | 11 ++ .../cyanogenmod/app/ISettingsManager.aidl | 27 +++ src/java/cyanogenmod/app/SettingsManager.java | 152 +++++++++++++++++ system-api/cm_system-current.txt | 9 + tests/AndroidManifest.xml | 9 + tests/res/values/strings.xml | 1 + .../tests/settings/CMSettingsManagerTest.java | 54 ++++++ 11 files changed, 440 insertions(+) create mode 100644 cm/lib/main/java/org/cyanogenmod/platform/internal/SettingsManagerService.java create mode 100644 src/java/cyanogenmod/app/ISettingsManager.aidl create mode 100644 src/java/cyanogenmod/app/SettingsManager.java create mode 100644 tests/src/org/cyanogenmod/tests/settings/CMSettingsManagerTest.java diff --git a/api/cm_current.txt b/api/cm_current.txt index 10011f2..179dd37 100644 --- a/api/cm_current.txt +++ b/api/cm_current.txt @@ -236,6 +236,14 @@ package cyanogenmod.app { field public static final int PROFILES_STATE_ENABLED = 1; // 0x1 } + public class SettingsManager { + method public static cyanogenmod.app.SettingsManager getInstance(android.content.Context); + method public void rebootDevice(); + method public void setAirplaneModeEnabled(boolean); + method public void setMobileDataEnabled(boolean); + method public void shutdownDevice(); + } + public class StatusBarPanelCustomTile implements android.os.Parcelable { ctor public StatusBarPanelCustomTile(java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, int, int, cyanogenmod.app.CustomTile, android.os.UserHandle); ctor public StatusBarPanelCustomTile(java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, int, int, cyanogenmod.app.CustomTile, android.os.UserHandle, long); @@ -285,6 +293,7 @@ package cyanogenmod.platform { public static final class Manifest.permission { ctor public Manifest.permission(); + field public static final java.lang.String MODIFY_SETTINGS = "cyanogenmod.permission.MODIFY_SETTINGS"; field public static final java.lang.String PUBLISH_CUSTOM_TILE = "cyanogenmod.permission.PUBLISH_CUSTOM_TILE"; } diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/SettingsManagerService.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/SettingsManagerService.java new file mode 100644 index 0000000..2aa57b9 --- /dev/null +++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/SettingsManagerService.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2011-2015 CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.platform.internal; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.IBinder; + +import android.os.IPowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.util.Log; +import com.android.server.SystemService; +import cyanogenmod.app.CMContextConstants; +import cyanogenmod.app.ISettingsManager; +import cyanogenmod.app.SettingsManager; + +import java.io.ByteArrayInputStream; +import java.security.PublicKey; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; + +/** {@hide} */ +public class SettingsManagerService extends SystemService { + + private static final String TAG = "CMSettingsService"; + + private Context mContext; + private TelephonyManager mTelephonyManager; + + public SettingsManagerService(Context context) { + super(context); + mContext = context; + publishBinderService(CMContextConstants.CM_SETTINGS_SERVICE, mService); + } + + @Override + public void onStart() { + mTelephonyManager = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + } + + private void enforceModifyNetworkSettingsPermission() { + mContext.enforceCallingOrSelfPermission(SettingsManager.MODIFY_NETWORK_SETTINGS_PERMISSION, + "You do not have permissions to change system network settings."); + } + + private void enforceShutdownPermission() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, + "You do not have permissions to shut down the device."); + } + + private final IBinder mService = new ISettingsManager.Stub() { + + @Override + public void setAirplaneModeEnabled(boolean enabled) { + enforceModifyNetworkSettingsPermission(); + /* + * We need to clear the caller's identity in order to + * allow this method call to modify settings + * not allowed by the caller's permissions. + */ + long token = clearCallingIdentity(); + setAirplaneModeEnabledInternal(enabled); + restoreCallingIdentity(token); + } + + @Override + public void setMobileDataEnabled(boolean enabled) { + enforceModifyNetworkSettingsPermission(); + /* + * We need to clear the caller's identity in order to + * allow this method call to modify settings + * not allowed by the caller's permissions. + */ + long token = clearCallingIdentity(); + setMobileDataEnabledInternal(enabled); + restoreCallingIdentity(token); + } + + @Override + public void shutdown() { + enforceShutdownPermission(); + /* + * We need to clear the caller's identity in order to + * allow this method call to modify settings + * not allowed by the caller's permissions. + */ + long token = clearCallingIdentity(); + shutdownInternal(false); + restoreCallingIdentity(token); + } + + @Override + public void reboot() { + enforceShutdownPermission(); + /* + * We need to clear the caller's identity in order to + * allow this method call to modify settings + * not allowed by the caller's permissions. + */ + long token = clearCallingIdentity(); + shutdownInternal(true); + restoreCallingIdentity(token); + } + }; + + private void setAirplaneModeEnabledInternal(boolean enabled) { + // Change the system setting + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, + enabled ? 1 : 0); + + // Post the intent + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", enabled); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } + + private void setMobileDataEnabledInternal(boolean enabled) { + mTelephonyManager.setDataEnabled(enabled); + } + + private void shutdownInternal(boolean reboot) { + IPowerManager pm = IPowerManager.Stub.asInterface( + ServiceManager.getService(Context.POWER_SERVICE)); + try { + if (reboot) { + pm.reboot(false, null, false); + } else { + pm.shutdown(false, false); + } + } catch (RemoteException e) { + Log.d(TAG, "Unable to shutdown."); + } + } +} + diff --git a/cm/res/AndroidManifest.xml b/cm/res/AndroidManifest.xml index c43c2b6..003c920 100644 --- a/cm/res/AndroidManifest.xml +++ b/cm/res/AndroidManifest.xml @@ -35,6 +35,13 @@ android:icon="@drawable/ic_launcher_cyanogenmod" android:protectionLevel="normal" /> + + + create a custom tile within quick settings panel Allows an app to publish a quick settings tile. + change system network settings + Allows an app to make changes to a restricted set of system network settings. + bind to a custom tile listener service Allows the app to bind to the top-level interface of a custom tile listener service. diff --git a/src/java/cyanogenmod/app/CMContextConstants.java b/src/java/cyanogenmod/app/CMContextConstants.java index 3ce899a..13dedcb 100644 --- a/src/java/cyanogenmod/app/CMContextConstants.java +++ b/src/java/cyanogenmod/app/CMContextConstants.java @@ -50,4 +50,15 @@ public final class CMContextConstants { * @hide */ public static final String CM_PROFILE_SERVICE = "profile"; + + /** + * Use with {@link android.content.Context#getSystemService} to retrieve a + * {@link cyanogenmod.app.SettingsManager} changing system settings. + * + * @see android.content.Context#getSystemService + * @see cyanogenmod.app.SettingsManager + * + * @hide + */ + public static final String CM_SETTINGS_SERVICE = "cmsettings"; } diff --git a/src/java/cyanogenmod/app/ISettingsManager.aidl b/src/java/cyanogenmod/app/ISettingsManager.aidl new file mode 100644 index 0000000..83d7bf0 --- /dev/null +++ b/src/java/cyanogenmod/app/ISettingsManager.aidl @@ -0,0 +1,27 @@ +/* //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 ISettingsManager +{ + void setAirplaneModeEnabled(boolean enabled); + void setMobileDataEnabled(boolean enabled); + void shutdown(); + void reboot(); +} diff --git a/src/java/cyanogenmod/app/SettingsManager.java b/src/java/cyanogenmod/app/SettingsManager.java new file mode 100644 index 0000000..2ab871b --- /dev/null +++ b/src/java/cyanogenmod/app/SettingsManager.java @@ -0,0 +1,152 @@ +/* + * 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; + +/** + *

+ * The SettingsManager allows applications to modify a subset of system settings. + *

+ */ +public class SettingsManager { + + private static ISettingsManager 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"; + + private static final String TAG = "SettingsManager"; + + private static SettingsManager sSettingsManagerInstance; + + private SettingsManager(Context context) { + Context appContext = context.getApplicationContext(); + if (appContext != null) { + mContext = appContext; + } else { + mContext = context; + } + sService = getService(); + } + + /** + * Get or create an instance of the {@link cyanogenmod.app.SettingsManager} + * @param context + * @return {@link SettingsManager} + */ + public static SettingsManager getInstance(Context context) { + if (sSettingsManagerInstance == null) { + sSettingsManagerInstance = new SettingsManager(context); + } + return sSettingsManagerInstance; + } + + /** @hide */ + static public ISettingsManager getService() { + if (sService != null) { + return sService; + } + IBinder b = ServiceManager.getService(CMContextConstants.CM_SETTINGS_SERVICE); + if (b != null) { + sService = ISettingsManager.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 { + getService().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 { + getService().setMobileDataEnabled(enabled); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + } + + /** + * 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 { + getService().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 { + getService().reboot(); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + } +} diff --git a/system-api/cm_system-current.txt b/system-api/cm_system-current.txt index 10011f2..179dd37 100644 --- a/system-api/cm_system-current.txt +++ b/system-api/cm_system-current.txt @@ -236,6 +236,14 @@ package cyanogenmod.app { field public static final int PROFILES_STATE_ENABLED = 1; // 0x1 } + public class SettingsManager { + method public static cyanogenmod.app.SettingsManager getInstance(android.content.Context); + method public void rebootDevice(); + method public void setAirplaneModeEnabled(boolean); + method public void setMobileDataEnabled(boolean); + method public void shutdownDevice(); + } + public class StatusBarPanelCustomTile implements android.os.Parcelable { ctor public StatusBarPanelCustomTile(java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, int, int, cyanogenmod.app.CustomTile, android.os.UserHandle); ctor public StatusBarPanelCustomTile(java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, int, int, cyanogenmod.app.CustomTile, android.os.UserHandle, long); @@ -285,6 +293,7 @@ package cyanogenmod.platform { public static final class Manifest.permission { ctor public Manifest.permission(); + field public static final java.lang.String MODIFY_SETTINGS = "cyanogenmod.permission.MODIFY_SETTINGS"; field public static final java.lang.String PUBLISH_CUSTOM_TILE = "cyanogenmod.permission.PUBLISH_CUSTOM_TILE"; } diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index d275d4f..8aae204 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -6,6 +6,8 @@ + + @@ -16,6 +18,13 @@ + + + + + + diff --git a/tests/res/values/strings.xml b/tests/res/values/strings.xml index d8ff6de..e07e6c1 100644 --- a/tests/res/values/strings.xml +++ b/tests/res/values/strings.xml @@ -1,4 +1,5 @@ CM Platform Tests + CM Platform Settings Tests diff --git a/tests/src/org/cyanogenmod/tests/settings/CMSettingsManagerTest.java b/tests/src/org/cyanogenmod/tests/settings/CMSettingsManagerTest.java new file mode 100644 index 0000000..adbf459 --- /dev/null +++ b/tests/src/org/cyanogenmod/tests/settings/CMSettingsManagerTest.java @@ -0,0 +1,54 @@ +package org.cyanogenmod.tests.settings; + +import org.cyanogenmod.tests.TestActivity; +import cyanogenmod.app.SettingsManager; + +/** + * Tests functionality added in {@link cyanogenmod.app.SettingsManager} + */ +public class CMSettingsManagerTest extends TestActivity { + SettingsManager mSettingsManager; + @Override + protected String tag() { + return null; + } + + @Override + protected Test[] tests() { + mSettingsManager = SettingsManager.getInstance(this); + return mTests; + } + + private Test[] mTests = new Test[] { + new Test("Test set airplane mode to on") { + public void run() { + mSettingsManager.setAirplaneModeEnabled(true); + } + }, + new Test("Test set airplane mode to off") { + public void run() { + mSettingsManager.setAirplaneModeEnabled(false); + } + }, + new Test("Test set mobile data to on") { + public void run() { + mSettingsManager.setMobileDataEnabled(true); + } + }, + new Test("Test set mobile data to off") { + public void run() { + mSettingsManager.setMobileDataEnabled(false); + } + }, + new Test("Test reboot the device") { + public void run() { + mSettingsManager.rebootDevice(); + } + }, + new Test("Test shutdown the device") { + public void run() { + mSettingsManager.shutdownDevice(); + } + } + }; +}