Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dfd9f8cc authored by Matt Garnes's avatar Matt Garnes Committed by Gerrit Code Review
Browse files

Allow alarms to be modified by 3rd parties.

- Export the ClockProvider.
- Move the ClockContract to the CM Platform SDK for external reference.
- Add three new permissions:
  - "cyanogenmod.alarmclock.permission.WRITE_ALARMS" - This
    system|signature only permission gives full write access to the
    ClockProvider.
  - "cyanogenmod.alarmclock.permission.READ_ALARMS" - Read only access to
    the ClockProvider.
  - "cyanogenmod.alarmclock.permission.MANAGE_ALARMS" - Access to perform
    a subset of all alarm changes such as turning on or off alarms.
- Protect the provider with the above permissions for read/write.
- Move the logic for updating an Alarm object directly into the Alarm
  class.
- Add a new IntentService to allow turning on and off alarms when
  started by an application with the MANAGE_ALARMS permission.

Change-Id: I399ce9f1073c6fdcb1711a11095f7c3a4bce2c6a
parent 628f5404
Loading
Loading
Loading
Loading
+33 −1
Original line number Original line Diff line number Diff line
@@ -21,6 +21,27 @@


    <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="21" ></uses-sdk>
    <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="21" ></uses-sdk>


    <permission
        android:name="cyanogenmod.alarmclock.permission.WRITE_ALARMS"
        android:permissionGroup="android.permission-group.SYSTEM_CLOCK"
        android:protectionLevel="system|signature"
        android:label="@string/permlab_write_alarms"
        android:description="@string/permdesc_write_alarms"/>

    <permission
        android:name="cyanogenmod.alarmclock.permission.MANAGE_ALARMS"
        android:permissionGroup="android.permission-group.SYSTEM_CLOCK"
        android:protectionLevel="normal"
        android:label="@string/permlab_manage_alarms"
        android:description="@string/permdesc_manage_alarms"/>

    <permission
        android:name="cyanogenmod.alarmclock.permission.READ_ALARMS"
        android:permissionGroup="android.permission-group.SYSTEM_CLOCK"
        android:protectionLevel="normal"
        android:label="@string/permlab_read_alarms"
        android:description="@string/permdesc_read_alarms"/>

    <application android:label="@string/app_label"
    <application android:label="@string/app_label"
                 android:icon="@mipmap/ic_launcher_alarmclock"
                 android:icon="@mipmap/ic_launcher_alarmclock"
                 android:requiredForAllUsers="true"
                 android:requiredForAllUsers="true"
@@ -28,7 +49,9 @@


        <provider android:name=".provider.ClockProvider"
        <provider android:name=".provider.ClockProvider"
                android:authorities="com.android.deskclock"
                android:authorities="com.android.deskclock"
                android:exported="false" />
                android:readPermission="cyanogenmod.alarmclock.permission.READ_ALARMS"
                android:writePermission="cyanogenmod.alarmclock.permission.WRITE_ALARMS"
                android:exported="true" />


        <provider android:name="com.android.deskclock.worldclock.db.DbCityProvider"
        <provider android:name="com.android.deskclock.worldclock.db.DbCityProvider"
                android:authorities="com.android.deskclock.worldclock.db"
                android:authorities="com.android.deskclock.worldclock.db"
@@ -256,6 +279,15 @@
                <action android:name="share_stopwatch" />
                <action android:name="share_stopwatch" />
            </intent-filter>
            </intent-filter>
        </service>
        </service>

        <service android:name="com.android.deskclock.AlarmModificationIntentService"
                android:exported="true"
                android:permission="cyanogenmod.alarmclock.permission.MANAGE_ALARMS"
                android:description="@string/alarm_modification_service_desc">
            <intent-filter>
                <action android:name="cyanogenmod.alarmclock.SET_ALARM_ENABLED" />
            </intent-filter>
        </service>
    </application>
    </application>
</manifest>
</manifest>
+8 −0
Original line number Original line Diff line number Diff line
@@ -98,4 +98,12 @@
    <string name="menu_item_widget_settings">Widget settings</string>
    <string name="menu_item_widget_settings">Widget settings</string>
    <string name="activity_not_found">Activity not found!</string>
    <string name="activity_not_found">Activity not found!</string>


    <!-- Permissions for reading/writing DeskClock alarms provider -->
    <string name="permlab_write_alarms">Directly change alarm clock alarms</string>
    <string name="permdesc_write_alarms">Allows this application to add, modify or delete all of your alarm clock alarms.</string>
    <string name="permlab_manage_alarms">Access and change your alarms</string>
    <string name="permdesc_manage_alarms">Allows this application to turn on or off your scheduled alarms.</string>
    <string name="permlab_read_alarms">Access and change your alarms</string>
    <string name="permdesc_read_alarms">Allows this application to add, modify and delete all of your alarm clock alarms.</string>
    <string name="alarm_modification_service_desc">An IntentService to allow manipulation of alarms by third parties.</string>
</resources>
</resources>
+5 −33
Original line number Original line Diff line number Diff line
@@ -1768,15 +1768,6 @@ public class AlarmClockFragment extends DeskClockFragment implements
        AlarmUtils.showTimeEditDialog(this, null);
        AlarmUtils.showTimeEditDialog(this, null);
    }
    }


    private static AlarmInstance setupAlarmInstance(Context context, Alarm alarm) {
        ContentResolver cr = context.getContentResolver();
        AlarmInstance newInstance = alarm.createInstanceAfter(Calendar.getInstance());
        newInstance = AlarmInstance.addInstance(cr, newInstance);
        // Register instance to state manager
        AlarmStateManager.registerInstance(context, newInstance, true);
        return newInstance;
    }

    private void asyncDeleteAlarm(final Alarm alarm) {
    private void asyncDeleteAlarm(final Alarm alarm) {
        final Context context = AlarmClockFragment.this.getActivity().getApplicationContext();
        final Context context = AlarmClockFragment.this.getActivity().getApplicationContext();
        final AsyncTask<Void, Void, Void> deleteTask = new AsyncTask<Void, Void, Void>() {
        final AsyncTask<Void, Void, Void> deleteTask = new AsyncTask<Void, Void, Void>() {
@@ -1818,7 +1809,7 @@ public class AlarmClockFragment extends DeskClockFragment implements
                                sDeskClockExtensions.addAlarm(
                                sDeskClockExtensions.addAlarm(
                                        AlarmClockFragment.this.getActivity().getApplicationContext(),
                                        AlarmClockFragment.this.getActivity().getApplicationContext(),
                                        newAlarm);
                                        newAlarm);
                                return setupAlarmInstance(context, newAlarm);
                                return Alarm.setupAlarmInstance(context, newAlarm);
                            }
                            }
                        }
                        }
                        return null;
                        return null;
@@ -1840,31 +1831,12 @@ public class AlarmClockFragment extends DeskClockFragment implements
                new AsyncTask<Void, Void, AlarmInstance>() {
                new AsyncTask<Void, Void, AlarmInstance>() {
                    @Override
                    @Override
                    protected AlarmInstance doInBackground(Void ... parameters) {
                    protected AlarmInstance doInBackground(Void ... parameters) {
                        ContentResolver cr = context.getContentResolver();
                        if (alarm != null) {

                            return alarm.processUpdate(context);
                        // Dismiss all old instances
                        } else {
                        AlarmStateManager.deleteAllInstances(context, alarm.id);
                        // Register/Update the ringtone uri
                        if (alarm.alert != null) {
                            try {
                                getActivity().getContentResolver().takePersistableUriPermission(
                                        alarm.alert, Intent.FLAG_GRANT_READ_URI_PERMISSION);
                            } catch (SecurityException ex) {
                                // Ignore
                            }
                        }

                        // Dismiss all old instances
                        AlarmStateManager.deleteAllInstances(context, alarm.id);

                        // Update alarm
                        Alarm.updateAlarm(cr, alarm);
                        if (alarm.enabled) {
                            return setupAlarmInstance(context, alarm);
                        }

                            return null;
                            return null;
                        }
                        }
                    }


                    @Override
                    @Override
                    protected void onPostExecute(AlarmInstance instance) {
                    protected void onPostExecute(AlarmInstance instance) {
+56 −0
Original line number Original line Diff line number Diff line
/**
 * 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 com.android.deskclock;

import android.app.IntentService;
import android.content.Intent;

import android.util.Log;
import com.android.deskclock.provider.Alarm;
import cyanogenmod.alarmclock.CyanogenModAlarmClock;

/**
 * Allows third parties to modify alarms.
 */
public class AlarmModificationIntentService extends IntentService {
    private static final String TAG = "AlarmModificationIntentService";

    public AlarmModificationIntentService() {
        super(TAG);
    }

    public AlarmModificationIntentService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        String action = intent.getAction();
        if (CyanogenModAlarmClock.ACTION_SET_ALARM_ENABLED.equals(action)) {
            long alarmId = intent.getLongExtra(CyanogenModAlarmClock.EXTRA_ALARM_ID, -1L);
            boolean enabled = intent.getBooleanExtra(CyanogenModAlarmClock.EXTRA_ENABLED, true);
            if (alarmId != -1L) {
                boolean success = Alarm.setAlarmEnabled(this, alarmId, enabled);
                if (!success) {
                    Log.w(TAG, "Failed to set alarm enabled status - alarmId: " + alarmId
                            + ", enabled: " + enabled);
                }
            } else {
                Log.w(TAG, "Unable to set alarm enabled status, invalid ID");
            }
        }
    }
}
+87 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,8 @@


package com.android.deskclock.provider;
package com.android.deskclock.provider;


import com.android.deskclock.alarms.AlarmStateManager;
import cyanogenmod.alarmclock.ClockContract;
import cyanogenmod.app.ProfileManager;
import cyanogenmod.app.ProfileManager;
import android.content.ContentResolver;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentUris;
@@ -214,6 +216,60 @@ public final class Alarm implements Parcelable, ClockContract.AlarmsColumns {
        return deletedRows == 1;
        return deletedRows == 1;
    }
    }


    /**
     * Set an existing alarm's enabled status, accounting for all required
     * follow up actions after this occurs. These include:
     *  - Delete all existing instances of this alarm
     *  - Update the ringtone URI to be accessible if the alarm is enabled.
     *  - Update the Alarms table to set the enabled flag.
     *  - If enabling the alarm, schedule a new instance for it.
     * @param context A Context to retrieve a ContentResolver.
     * @param alarmId The ID of the alarm to change the enabled state of.
     * @param enabled if true, set the alarm to enabled. Otherwise, disabled the alarm.
     * @return true if the alarm enabled state change was successful.
     */
    public static boolean setAlarmEnabled(Context context, long alarmId, boolean enabled) {
        ContentResolver contentResolver = context.getContentResolver();
        Alarm alarm = getAlarm(contentResolver, alarmId);
        // If this alarm does not exist, we can't update it's enabled status.
        if (alarm == null || alarm.enabled == enabled) {
            return false;
        } else if (alarm.enabled == enabled) {
            // While we didn't "successfully" update anything, the current state
            // is what the caller requested, so return true.
            return true;
        }

        // Set the new enabled state.
        alarm.enabled = enabled;
        // Persist the change and schedule the next instance.
        AlarmInstance nextInstance = alarm.processUpdate(context);

        if (alarm.enabled) {
            // If the alarm's new state is enabled, processUpdate must
            // result in a new instance being created.
            return nextInstance != null;
        } else {
            // processUpdate handled disabling the alarm, return true.
            return true;
        }
    }

    /**
     * Schedule the next instance of this alarm.
     * @param context A Context to retrieve a ContentResolver.
     * @param alarm The alarm to set enabled/disabled.
     * @return The new AlarmInstance that was created.
     */
    public static AlarmInstance setupAlarmInstance(Context context, Alarm alarm) {
        ContentResolver cr = context.getContentResolver();
        AlarmInstance newInstance = alarm.createInstanceAfter(Calendar.getInstance());
        newInstance = AlarmInstance.addInstance(cr, newInstance);
        // Register instance to state manager
        AlarmStateManager.registerInstance(context, newInstance, true);
        return newInstance;
    }

    public static final Parcelable.Creator<Alarm> CREATOR = new Parcelable.Creator<Alarm>() {
    public static final Parcelable.Creator<Alarm> CREATOR = new Parcelable.Creator<Alarm>() {
        public Alarm createFromParcel(Parcel p) {
        public Alarm createFromParcel(Parcel p) {
            return new Alarm(p);
            return new Alarm(p);
@@ -363,6 +419,37 @@ public final class Alarm implements Parcelable, ClockContract.AlarmsColumns {
        return nextInstanceTime;
        return nextInstanceTime;
    }
    }


    /**
     * Fully handle the logic to persist changes that have been made to this alarm.
     * Deletes all instances, re-grants any ringtone URI permissions, updates
     * the alarm in the DB, and recreates the next instance of this alarm.
     * @param context A Context to retrieve a ContentResolver.
     * @return The instance that was created, if the alarm is enabled and instance
     * creation was successful. Returns null otherwise.
     */
    public AlarmInstance processUpdate(Context context) {
        ContentResolver contentResolver = context.getContentResolver();
        // Register/Update the ringtone uri
        if (alert != null) {
            try {
                contentResolver.takePersistableUriPermission(
                        alert, Intent.FLAG_GRANT_READ_URI_PERMISSION);
            } catch (SecurityException ex) {
                // Ignore
            }
        }

        // Dismiss all old instances
        AlarmStateManager.deleteAllInstances(context, id);

        // Update alarm
        Alarm.updateAlarm(contentResolver, this);
        if (enabled) {
            return setupAlarmInstance(context, this);
        }
        return null;
    }

    @Override
    @Override
    public boolean equals(Object o) {
    public boolean equals(Object o) {
        if (!(o instanceof Alarm)) return false;
        if (!(o instanceof Alarm)) return false;
Loading