diff --git a/app/build.gradle b/app/build.gradle index d36ed4e..e818118 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,10 +24,11 @@ dependencies { androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile 'com.android.support:appcompat-v7:26.+' + compile 'com.android.support:appcompat-v7:26.0.1' + compile 'com.android.support:design:26.0.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' - compile 'com.google.android.gms:play-services-places:11.0.2' - compile 'com.google.android.gms:play-services-location:11.0.2' - compile 'com.android.support:recyclerview-v7:26.+' + compile 'com.google.android.gms:play-services-places:11.0.4' + compile 'com.google.android.gms:play-services-location:11.0.4' + compile 'com.android.support:recyclerview-v7:26.0.1' testCompile 'junit:junit:4.12' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index df88dfc..a501779 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,12 +13,11 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - + android:value="@string/your_google_places_api_key" /> - + @@ -32,11 +31,13 @@ android:exported="false" /> + android:exported="true" /> + - + diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/AlarmBroadcastReceiver.java b/app/src/main/java/com/mdg/droiders/samagra/shush/AlarmBroadcastReceiver.java deleted file mode 100644 index c8a82a8..0000000 --- a/app/src/main/java/com/mdg/droiders/samagra/shush/AlarmBroadcastReceiver.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.mdg.droiders.samagra.shush; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.media.AudioManager; - -import com.mdg.droiders.samagra.shush.utils.RingerUtils; - -/** - * Created by rohan on 12/8/17. - *
- * Broadcast Receiver that receives a broadcast from the alarm manager to silence/un-silence the phone. - */ - -public class AlarmBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - boolean shush = intent.getBooleanExtra( - context.getString(R.string.alarm_intent_extra_key), false); - if (shush) { - RingerUtils.setRingerMode(context, AudioManager.RINGER_MODE_SILENT); - } else { - RingerUtils.setRingerMode(context, AudioManager.RINGER_MODE_NORMAL); - } - } -} diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/AlarmScheduler.java b/app/src/main/java/com/mdg/droiders/samagra/shush/AlarmScheduler.java index 134b771..9e354b2 100644 --- a/app/src/main/java/com/mdg/droiders/samagra/shush/AlarmScheduler.java +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/AlarmScheduler.java @@ -4,8 +4,15 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.util.Log; +import com.mdg.droiders.samagra.shush.receivers.AlarmBroadcastReceiver; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; import java.util.List; +import java.util.Locale; /** * Created by rohan on 12/8/17. @@ -14,12 +21,11 @@ */ public class AlarmScheduler { - - /** - * Request code for scheduling an alarm - * Will be taken from the database - */ - private static final int REQUEST_CODE = 12; + private static final SimpleDateFormat LOG_DATE_FORMAT + = new SimpleDateFormat("hh:mm a, EE ,dd MMM yyyy", Locale.ENGLISH); + private static final int START_ALARM_ID_INCREMENTER = 10000; + private static final int END_ALARM_ID_INCREMENTER = 1000; + private static final String LOG_TAG = "Samagra/AS/"; private AlarmManager alarmManager; private Context context; @@ -29,53 +35,295 @@ public AlarmScheduler(Context context) { this.context = context; } + /** + * Sets an alarm to shush the phone for a single day. + * + * @param startTime The time at which phone is to be shushed + * @param endTime The time at which phone is to be un-shushed + * @param day int type for week days starting from monday and zero indexed. + * For e.g. - Monday is 0, Tuesday is 1, ... and so on + * @param rowID Unique primary key of the shush alarm row. + */ + public void setSingleDayAlarm(Calendar startTime, Calendar endTime, int day, Integer rowID) { + if (day > 6 || rowID == null) { + return; + } + boolean[] days = new boolean[7]; + for (int i = 0; i < 7; i++) { + days[i] = day == i; + } + setWeeklyAlarm(startTime, endTime, days, rowID); + } + + /** + * Cancels a shush alarm for a particular day if it exists. + * + * @param day int type for week days starting from monday and zero indexed. + * For e.g. - Monday is 0, Tuesday is 1, ... and so on + * @param rowID Unique primary key of the shush alarm row. + */ + public void cancelSingleAlarm(int day, Integer rowID) { + if (rowID == null) { + return; + } + alarmManager.cancel(getDefaultPendingIntent(getStartDayID(day, rowID))); + alarmManager.cancel(getDefaultPendingIntent(getEndDayID(day, rowID))); + } + + /** + * Sets a weekly alarm to silence your phone for days given by boolean array days. + *
+ * NOTE : This method only schedules alarms and does NOT cancels for some day whose + * corresponding index in days array is false. To cancel alarms + * use {@link #cancelSingleAlarm(int, Integer)} + *

+ * Uses : {@link #setAlarm(long, long, Integer, Integer)} to set each alarm one by one. + * + * @param startTime The time of day at which phone is to be shushed + * @param endTime The time of day at which phone is to be un-shushed + * @param days A boolean array that is true if the alarm is to be + * scheduled for the corresponding day. i.e. days[0] is + * set to be true then a weekly alarm will be set such + * that it will shush the phone every Monday. + * @param rowID Unique primary key of the shush alarm row. + */ + public void setWeeklyAlarm(Calendar startTime, Calendar endTime, boolean[] days, Integer rowID) { + // To avoid various exceptions + if (rowID == null || days.length != 7) { + return; + } + // Instance of calendar that holds current system time + Calendar currentTime = Calendar.getInstance(); + + // Set YEAR, MONTH and DAY fields of startTime + // and endTime to today's YEAR, MONTH and DAY respectively + startTime.set(Calendar.YEAR, currentTime.get(Calendar.YEAR)); + startTime.set(Calendar.MONTH, currentTime.get(Calendar.MONTH)); + startTime.set(Calendar.DAY_OF_MONTH, currentTime.get(Calendar.DAY_OF_MONTH)); + endTime.set(Calendar.YEAR, currentTime.get(Calendar.YEAR)); + endTime.set(Calendar.MONTH, currentTime.get(Calendar.MONTH)); + endTime.set(Calendar.DAY_OF_MONTH, currentTime.get(Calendar.DAY_OF_MONTH)); + Log.d(LOG_TAG + "now", LOG_DATE_FORMAT.format(currentTime.getTime())); + + // If startTime's hour is ahead of endTime's, assume that + // endTime is of that of next day + if (startTime.get(Calendar.HOUR_OF_DAY) + > endTime.get(Calendar.HOUR_OF_DAY)) { + endTime.add(Calendar.DAY_OF_MONTH, 1); + } + + // If endTime is in the past increment both start and end time + if (endTime.compareTo(currentTime) < 0) { + startTime.add(Calendar.DAY_OF_MONTH, 1); + endTime.add(Calendar.DAY_OF_MONTH, 1); + } + + // get Day index of start Time + int dayForAlarm = getDay(startTime); + for (int j = dayForAlarm; j < 7 + dayForAlarm; j++) { + int day = j % 7; + Log.d(LOG_TAG + "Day-Val", String.valueOf(day)); + if (days[day]) { + setAlarm(startTime.getTimeInMillis(), + endTime.getTimeInMillis(), + getStartDayID(day, rowID), + getEndDayID(day, rowID)); + } + // Update startTime and endTime + startTime.add(Calendar.DAY_OF_MONTH, 1); + endTime.add(Calendar.DAY_OF_MONTH, 1); + } + } + + /** + * Sets a weekly alarm to silence your phone for every day. + *

+ * Uses : {@link #setWeeklyAlarm(Calendar, Calendar, boolean[], Integer)} + * to set weekly alarms. + * + * @param startHour The hour at which phone is to be shushed in 24 hour format + * @param startMinutes The minute at which phone is to be shushed + * @param endHour The hour at which phone is to be un-shushed in 24 hour format + * @param endMinutes The minute at which phone is to be un-shushed + * @param days A boolean array that is true if the alarm is to be + * scheduled for the corresponding day. i.e. days[0] is set to be true + * then a weekly alarm will be set such that it will shush + * the phone every Monday. + * @param rowID Unique primary key of the shush alarm row. + */ + public void setWeeklyAlarm(int startHour, int startMinutes, int endHour, + int endMinutes, boolean[] days, Integer rowID) { + if (rowID == null || days.length != 7) { + return; + } + Calendar startTime = Calendar.getInstance(); + Calendar endTime = Calendar.getInstance(); + startTime.set(Calendar.HOUR_OF_DAY, startHour); + startTime.set(Calendar.MINUTE, startMinutes); + endTime.set(Calendar.HOUR_OF_DAY, endHour); + endTime.set(Calendar.MINUTE, endMinutes); + setWeeklyAlarm(startTime, endTime, days, rowID); + } + /** * Set an alarm to silence the phone. + *

+ * Uses : {@link #setAlarm(long, Integer, boolean)} to set start alarm + * and the end alarm * * @param startTimeInMillis The time at which phone is to be shushed * @param endTimeInMillis The time at which phone is to be un-shushed + * @param startAlarmID The id that uniquely identifies the start time alarm + * @param endAlarmID The id that uniquely identifies the end time alarm */ - public void setAlarm(long startTimeInMillis, long endTimeInMillis) { + public void setAlarm(long startTimeInMillis, long endTimeInMillis, + Integer startAlarmID, Integer endAlarmID) { if (startTimeInMillis >= endTimeInMillis) { return; } - alarmManager.set( - AlarmManager.RTC_WAKEUP, - startTimeInMillis, - getDefaultPendingIntent(true) - ); - alarmManager.set( + if (startAlarmID == null || endAlarmID == null) { + return; + } + if (startAlarmID.equals(endAlarmID)) { + Log.e(LOG_TAG + "error", "start alarm id and end alarm ids are same"); + return; + } + + Log.d(LOG_TAG + "startTime", LOG_DATE_FORMAT.format(new Date(startTimeInMillis))); + Log.d(LOG_TAG + "endTime", LOG_DATE_FORMAT.format(new Date(endTimeInMillis))); + setAlarm(startTimeInMillis, startAlarmID, true); + setAlarm(endTimeInMillis, endAlarmID, false); + } + + /** + * Set an alarm to silence the phone. + * + * @param timeInMillis The time at which phone is to be shushed/un-shushed + * @param alarmID The alarm id used when scheduling the alarm + * @param shush A boolean indicating whether or not to + * silence the phone when the alarm is fired by the system. + * Silent mode will be activated if shouldShush is set to be true + * @see #setAlarm(long, long, Integer, Integer) + */ + public void setAlarm(long timeInMillis, Integer alarmID, boolean shush) { + if (alarmID == null) { + return; + } + + alarmManager.setExact( AlarmManager.RTC_WAKEUP, - endTimeInMillis, - getDefaultPendingIntent(false) + timeInMillis, + getDefaultPendingIntent(shush, timeInMillis, alarmID) ); } /** * Sets multiple alarms at once. *

- * Uses : {@link #setAlarm(long, long)} to set each alarm one by one. + * Uses : {@link #setAlarm(long, long, Integer, Integer)} to set each alarm one by one. * * @param startTimesInMillis The list of start times at which phone is to be shushed * @param endTimesInMillis The list of end times at which phone is to be un-shushed */ - public void setAlarms(List startTimesInMillis, List endTimesInMillis) { + public void setAlarms(List startTimesInMillis, List endTimesInMillis, + List startAlarmIds, List endAlarmIds) { if (startTimesInMillis.size() != endTimesInMillis.size()) { return; } for (int i = 0; i < startTimesInMillis.size(); i++) { - setAlarm(startTimesInMillis.get(i), endTimesInMillis.get(i)); + setAlarm(startTimesInMillis.get(i), endTimesInMillis.get(i), + startAlarmIds.get(i), endAlarmIds.get(i)); } } - private PendingIntent getDefaultPendingIntent(boolean shouldSilence) { + /** + * Get a default pending intent without any extras. + *
Used to cancel any existing alarm whose alarm id is known. + * + * @param alarmID The alarm id used when scheduling the alarm + * @return a pending intent that can be used to cancel + * any alarm which has same pending intent + * @see #getDefaultPendingIntent(boolean, long, int) + */ + private PendingIntent getDefaultPendingIntent(int alarmID) { PendingIntent pendingIntent; Intent intent = new Intent(context, AlarmBroadcastReceiver.class); - intent.putExtra(context.getString(R.string.alarm_intent_extra_key), shouldSilence); pendingIntent = PendingIntent.getBroadcast( - context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); + context, alarmID, intent, PendingIntent.FLAG_UPDATE_CURRENT + ); return pendingIntent; } + + /** + * Get a complete pending intent with all the extra values. + *
Used to schedule an alarm for future. + * + * @param shouldShush A boolean indicating whether or not to + * silence the phone when the alarm is fired by the system. + * Silent mode will be activated if shouldShush is set to be true + * @param timeInMillis The "timeInMillis" at which the alarm is expected to go off. + * It is used to schedule a future alarm for next week. + * @param alarmId An int that uniquely identifies this alarm, so that the alarm + * may be updated/canceled in the future using the same. + * @return A pending intent used to schedule alarms by + * {@link #setAlarm(long, long, Integer, Integer)} method + * @see #getDefaultPendingIntent(int) + */ + private PendingIntent getDefaultPendingIntent(boolean shouldShush, long timeInMillis, int alarmId) { + PendingIntent pendingIntent; + + Intent intent = new Intent(context, AlarmBroadcastReceiver.class); + intent.putExtra(context.getString(R.string.alarm_intent_boolean_extra_key), shouldShush); + intent.putExtra(context.getString(R.string.alarm_intent_long_extra_key), timeInMillis); + intent.putExtra(context.getString(R.string.alarm_intent_int_extra_key), alarmId); + pendingIntent = PendingIntent.getBroadcast( + context, alarmId, intent, PendingIntent.FLAG_UPDATE_CURRENT + ); + + return pendingIntent; + } + + /** + * Get day ID for start alarms(shush types) that are scheduled by alarm manager. + * The ID returned identifies a particular day of a particular shush alarm. + * + * @param day int type for week days starting from monday and zero indexed. + * For e.g. - Monday is 0, Tuesday is 1, ... and so on + * @param rowID Unique primary key of the shush alarm row. + * @return The ID used by {@link AlarmManager} to schedule weekly alarms + */ + private int getStartDayID(int day, int rowID) { + return rowID + (day + 1) * START_ALARM_ID_INCREMENTER; + } + + /** + * Get day ID for end alarms(un-shush types) that are scheduled by alarm manager. + * The ID returned identifies a particular day of a particular shush alarm. + * + * @param day int type for week days starting from monday and zero indexed. + * For e.g. - Monday is 0, Tuesday is 1, ... and so on + * @param rowID Unique primary key of the shush alarm row. + * @return The ID used by {@link AlarmManager} to schedule weekly alarms + */ + private int getEndDayID(int day, int rowID) { + return rowID + (day + 1) * END_ALARM_ID_INCREMENTER; + } + + /** + * Get day field of calendar indexed at 0 such that start day of week is monday. + * + * @param calendar The calendar whose day is to be returned + * @return int type for week days starting from monday and zero indexed. + * For e.g. - Monday is 0, Tuesday is 1, ... and so on + */ + private int getDay(Calendar calendar) { + int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); + dayOfWeek -= 2; + if (dayOfWeek == -1) { + dayOfWeek += 7; + } + return dayOfWeek; + } } diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/GeofenceBroadcastReceiver.java b/app/src/main/java/com/mdg/droiders/samagra/shush/GeofenceBroadcastReceiver.java deleted file mode 100644 index e5a6928..0000000 --- a/app/src/main/java/com/mdg/droiders/samagra/shush/GeofenceBroadcastReceiver.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.mdg.droiders.samagra.shush; - -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.TaskStackBuilder; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.graphics.BitmapFactory; -import android.media.AudioManager; -import android.support.v4.app.NotificationCompat; -import android.util.Log; - -import com.google.android.gms.location.Geofence; -import com.google.android.gms.location.GeofencingEvent; -import com.mdg.droiders.samagra.shush.utils.RingerUtils; - -public class GeofenceBroadcastReceiver extends BroadcastReceiver { - - private static final String LOG_TAG = GeofenceBroadcastReceiver.class.getName(); - - /*** - * Handles the Broadcast message sent when the Geofence Transition is triggered - * Careful here though, this is running on the main thread so make sure you start an AsyncTask for - * anything that takes longer than say 10 second to run - * - * @param context - * @param intent - */ - @Override - public void onReceive(Context context, Intent intent) { - //get the geofencing event sent from the intent - GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); - - if (geofencingEvent.hasError()) { - Log.e(LOG_TAG, String.format("Error Code : %s", geofencingEvent.getErrorCode())); - return; - } - - //get the transition type - int geoFenceTransition = geofencingEvent.getGeofenceTransition(); - - //Check which transition type has triggered the event - if (geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) { - RingerUtils.setRingerMode(context, AudioManager.RINGER_MODE_SILENT); - } else if (geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) { - RingerUtils.setRingerMode(context, AudioManager.RINGER_MODE_NORMAL); - } else { - Log.e(LOG_TAG, String.format("Unknown Transition , %d", geoFenceTransition)); - return; - } - - //Send the notification - sendNotification(context, geoFenceTransition); - } - - /** - * Posts a notification in the notification bar when a transition is detected - * Uses different icon drawables for different transition types - * If the user clicks the notification, control goes to the MainActivity - * - * @param context The calling context for building a task stack - * @param transition The geofence transition type, can be Geofence.GEOFENCE_TRANSITION_ENTER - * or Geofence.GEOFENCE_TRANSITION_EXIT - */ - private void sendNotification(Context context, int transition) { - //create an explicit content intent that starts the main activity - Intent notificationIntent = new Intent(context, MainActivity.class); - - //Construct a task stack - TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); - - //Adding mainActivity to the task stack as the parent task - stackBuilder.addParentStack(MainActivity.class); - - //Push the content intent onto the stack - stackBuilder.addNextIntent(notificationIntent); - - //Get a pending intent consisting the entire backstack - PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); - - //Get a notification builder - NotificationCompat.Builder builder = new NotificationCompat.Builder(context); - - //Check the transition type to display the relevant icon image - - if (transition == Geofence.GEOFENCE_TRANSITION_ENTER) { - builder.setSmallIcon(R.drawable.ic_volume_off_white_24dp) - .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), - R.drawable.ic_volume_off_white_24dp)) - .setContentTitle(context.getString(R.string.silent_mode_activated)); - } else if (transition == Geofence.GEOFENCE_TRANSITION_EXIT) { - builder.setSmallIcon(R.drawable.ic_volume_up_white_24dp) - .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), - R.drawable.ic_volume_up_white_24dp)) - .setContentTitle(context.getString(R.string.back_to_normal)); - } - - // Continue building the notification - builder.setContentText(context.getString(R.string.touch_to_relaunch)); - builder.setContentIntent(notificationPendingIntent); - - //Auto remove the notification if the user touches it - builder.setAutoCancel(true); - - NotificationManager notificationManager = (NotificationManager) - context.getSystemService(Context.NOTIFICATION_SERVICE); - - notificationManager.notify(0, builder.build()); - } - -} diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/Geofencing.java b/app/src/main/java/com/mdg/droiders/samagra/shush/Geofencing.java index 50daa80..8b345f4 100644 --- a/app/src/main/java/com/mdg/droiders/samagra/shush/Geofencing.java +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/Geofencing.java @@ -14,6 +14,7 @@ import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.places.Place; import com.google.android.gms.location.places.PlaceBuffer; +import com.mdg.droiders.samagra.shush.receivers.GeofenceBroadcastReceiver; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/MainActivity.java b/app/src/main/java/com/mdg/droiders/samagra/shush/activities/MainActivity.java similarity index 95% rename from app/src/main/java/com/mdg/droiders/samagra/shush/MainActivity.java rename to app/src/main/java/com/mdg/droiders/samagra/shush/activities/MainActivity.java index efebcd5..88d70d9 100644 --- a/app/src/main/java/com/mdg/droiders/samagra/shush/MainActivity.java +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/activities/MainActivity.java @@ -1,4 +1,4 @@ -package com.mdg.droiders.samagra.shush; +package com.mdg.droiders.samagra.shush.activities; import android.Manifest; import android.app.NotificationManager; @@ -38,6 +38,9 @@ import com.google.android.gms.location.places.PlaceBuffer; import com.google.android.gms.location.places.Places; import com.google.android.gms.location.places.ui.PlacePicker; +import com.mdg.droiders.samagra.shush.Geofencing; +import com.mdg.droiders.samagra.shush.R; +import com.mdg.droiders.samagra.shush.adapters.PlaceListAdapter; import com.mdg.droiders.samagra.shush.data.PlacesContract; import java.util.ArrayList; @@ -58,6 +61,7 @@ public class MainActivity extends AppCompatActivity implements private PlaceListAdapter mAdapter; private RecyclerView mRecyclerView; private Button addPlaceButton; + private Button addAlarmButton; private GoogleApiClient mClient; private Geofencing mGeofencing; private boolean mIsEnabled; @@ -110,6 +114,15 @@ public void onClick(View view) { } }); + addAlarmButton = findViewById(R.id.add_alarm_button); + + addAlarmButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + startActivity(new Intent(MainActivity.this, TimeListActivity.class)); + } + }); + mClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/activities/TimeListActivity.java b/app/src/main/java/com/mdg/droiders/samagra/shush/activities/TimeListActivity.java new file mode 100644 index 0000000..78a681a --- /dev/null +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/activities/TimeListActivity.java @@ -0,0 +1,73 @@ +package com.mdg.droiders.samagra.shush.activities; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.MenuItem; +import android.view.View; + +import com.mdg.droiders.samagra.shush.R; +import com.mdg.droiders.samagra.shush.adapters.TimeListAdapter; + +public class TimeListActivity extends AppCompatActivity { + + private RecyclerView timeRecycler; + private TimeListAdapter timeListAdapter; + private boolean isCursorRefreshed; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_time_list); + setUpActionBar(); + timeRecycler = findViewById(R.id.time_list_recycler); + findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (timeListAdapter != null) { + timeListAdapter.addItem(); + } + } + }); + timeListAdapter = new TimeListAdapter(this); + // Cursor is refreshed in the constructor + isCursorRefreshed = true; + timeRecycler.setLayoutManager(new LinearLayoutManager(this)); + timeRecycler.setAdapter(timeListAdapter); + } + + @Override + protected void onResume() { + super.onResume(); + if (!isCursorRefreshed) { + timeListAdapter.refreshCursor(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + switch (id) { + case android.R.id.home: { + finish(); + return true; + } + } + return false; + } + + @Override + protected void onStop() { + super.onStop(); + timeListAdapter.closeCursor(); + isCursorRefreshed = false; + } + + private void setUpActionBar() { + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + } + +} diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/PlaceListAdapter.java b/app/src/main/java/com/mdg/droiders/samagra/shush/adapters/PlaceListAdapter.java similarity index 97% rename from app/src/main/java/com/mdg/droiders/samagra/shush/PlaceListAdapter.java rename to app/src/main/java/com/mdg/droiders/samagra/shush/adapters/PlaceListAdapter.java index ac150a7..a07c22d 100644 --- a/app/src/main/java/com/mdg/droiders/samagra/shush/PlaceListAdapter.java +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/adapters/PlaceListAdapter.java @@ -1,4 +1,4 @@ -package com.mdg.droiders.samagra.shush; +package com.mdg.droiders.samagra.shush.adapters; import android.content.Context; import android.support.v7.widget.RecyclerView; @@ -8,6 +8,7 @@ import android.widget.TextView; import com.google.android.gms.location.places.PlaceBuffer; +import com.mdg.droiders.samagra.shush.R; /** * Created by samagra on 20/7/17. diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/adapters/TimeListAdapter.java b/app/src/main/java/com/mdg/droiders/samagra/shush/adapters/TimeListAdapter.java new file mode 100644 index 0000000..05cc483 --- /dev/null +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/adapters/TimeListAdapter.java @@ -0,0 +1,242 @@ +package com.mdg.droiders.samagra.shush.adapters; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.mdg.droiders.samagra.shush.AlarmScheduler; +import com.mdg.droiders.samagra.shush.R; +import com.mdg.droiders.samagra.shush.data.PlacesContract; +import com.mdg.droiders.samagra.shush.interfaces.TimeAdapterNotifier; +import com.mdg.droiders.samagra.shush.viewholders.TimeRowHolder; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; + +/** + * Created by rohan on 19/8/17. + * Adapter that binds data to the view for time entries in the Shush app. + */ +public class TimeListAdapter extends RecyclerView.Adapter + implements TimeAdapterNotifier { + + // The format which is used to display date in each row + public static final SimpleDateFormat DISPLAY_DATE_FORMAT = + new SimpleDateFormat("hh:mm a", Locale.ENGLISH); + private static final String LOG_TAG = "Samagra/TLA/"; + + private Context mContext; + private Cursor timeDataCursor; + private AlarmScheduler alarmScheduler; + + public TimeListAdapter(Context mContext) { + this.mContext = mContext; + refreshCursor(); + alarmScheduler = new AlarmScheduler(mContext); + } + + @Override + public TimeRowHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View row = LayoutInflater.from(mContext) + .inflate(R.layout.time_picker_row, parent, false); + return new TimeRowHolder(row, this, mContext); + } + + @Override + public void onBindViewHolder(TimeRowHolder holder, int position) { + if (position == timeDataCursor.getCount()) { + holder.itemView.setVisibility(View.INVISIBLE); + return; + } + // isBound set to false so that listeners do not react to set events in this method. + holder.isBound = false; + holder.itemView.setVisibility(View.VISIBLE); + if (timeDataCursor.moveToPosition(position)) { + + // Retrieving the whole row from the database and Updating the holder + holder.id = timeDataCursor.getInt( + timeDataCursor.getColumnIndexOrThrow(PlacesContract.TimeEntry._ID) + ); + holder.startTime.setText(timeDataCursor.getString( + timeDataCursor.getColumnIndexOrThrow(PlacesContract.TimeEntry.COLUMN_START_TIME) + )); + holder.endTime.setText(timeDataCursor.getString( + timeDataCursor.getColumnIndexOrThrow(PlacesContract.TimeEntry.COLUMN_END_TIME) + )); + for (int i = 0; i < 7; i++) { + int day = timeDataCursor.getInt( + timeDataCursor.getColumnIndexOrThrow(getColumnFromDay(i)) + ); + holder.days[i].setChecked(day == 1); + } + + // All the set events have occurred, now we can again start listening. + holder.isBound = true; + } + + } + + @Override + public int getItemCount() { + // An extra item is to make space for the fab + return timeDataCursor.getCount() + 1; + } + + /** + * Adds a new row in the recycler as well as in the db with some default values. + */ + public void addItem() { + Calendar calendar = Calendar.getInstance(); + ContentValues values = new ContentValues(); + values.put(PlacesContract.TimeEntry.COLUMN_START_TIME, + DISPLAY_DATE_FORMAT.format(calendar.getTime())); + values.put(PlacesContract.TimeEntry.COLUMN_END_TIME, + DISPLAY_DATE_FORMAT.format(calendar.getTime())); + values.put(PlacesContract.TimeEntry.COLUMN_MONDAY, 0); + values.put(PlacesContract.TimeEntry.COLUMN_TUESDAY, 0); + values.put(PlacesContract.TimeEntry.COLUMN_WEDNESDAY, 0); + values.put(PlacesContract.TimeEntry.COLUMN_THURSDAY, 0); + values.put(PlacesContract.TimeEntry.COLUMN_FRIDAY, 0); + values.put(PlacesContract.TimeEntry.COLUMN_SATURDAY, 0); + values.put(PlacesContract.TimeEntry.COLUMN_SUNDAY, 0); + mContext.getContentResolver().insert( + PlacesContract.TimeEntry.CONTENT_URI, + values + ); + closeCursor(); + refreshCursor(); + notifyItemInserted(timeDataCursor.getCount() - 1); + } + + /** + * Make a query to the database to get the updated result in the local cursor instance. + */ + public void refreshCursor() { + timeDataCursor = mContext.getContentResolver().query( + PlacesContract.TimeEntry.CONTENT_URI, null, null, null, null); + } + + /** + * Close the cursor to release its resources. + * The cursor should be closed in onStop() and refreshed in onResume(). + */ + public void closeCursor() { + timeDataCursor.close(); + } + + @Override + public void notifyTimeChanged(TimeRowHolder holder) { + ContentValues values = new ContentValues(); + String startTimeString = holder.startTime.getText().toString(); + String endTimeString = holder.endTime.getText().toString(); + values.put(PlacesContract.TimeEntry.COLUMN_START_TIME, startTimeString); + values.put(PlacesContract.TimeEntry.COLUMN_END_TIME, endTimeString); + + mContext.getContentResolver().update( + Uri.withAppendedPath(PlacesContract.TimeEntry.CONTENT_URI, + String.valueOf(holder.id)), values, null, null + ); + + Calendar startTime = Calendar.getInstance(); + Calendar endTime = Calendar.getInstance(); + setTimesFromHolder(holder, startTime, endTime); + closeCursor(); + refreshCursor(); + alarmScheduler.setWeeklyAlarm(startTime, endTime, getDayArr(holder), holder.id); + } + + @Override + public void notifyDayAlarmSet(TimeRowHolder holder, int day) { + + ContentValues values = new ContentValues(); + values.put(getColumnFromDay(day), true); + mContext.getContentResolver().update( + Uri.withAppendedPath( + PlacesContract.TimeEntry.CONTENT_URI, String.valueOf(holder.id) + ), values, null, null + ); + + Calendar startTime = Calendar.getInstance(); + Calendar endTime = Calendar.getInstance(); + setTimesFromHolder(holder, startTime, endTime); + alarmScheduler.setSingleDayAlarm(startTime, endTime, day, holder.id); + } + + @Override + public void notifyDayAlarmCancelled(TimeRowHolder holder, int day) { + + ContentValues values = new ContentValues(); + values.put(getColumnFromDay(day), false); + mContext.getContentResolver().update( + Uri.withAppendedPath(PlacesContract.TimeEntry.CONTENT_URI, String.valueOf(holder.id)) + , values, null, null + ); + + Calendar startTime = Calendar.getInstance(); + Calendar endTime = Calendar.getInstance(); + setTimesFromHolder(holder, startTime, endTime); + alarmScheduler.cancelSingleAlarm(day, holder.id); + } + + /** + * Returns the corresponding column of the day in db. + * + * @param day The day for which the column is to be returned + * @return The column corresponding to the given day + */ + private String getColumnFromDay(int day) { + switch (day) { + case 0: { + return PlacesContract.TimeEntry.COLUMN_MONDAY; + } + case 1: { + return PlacesContract.TimeEntry.COLUMN_TUESDAY; + } + case 2: { + return PlacesContract.TimeEntry.COLUMN_WEDNESDAY; + } + case 3: { + return PlacesContract.TimeEntry.COLUMN_THURSDAY; + } + case 4: { + return PlacesContract.TimeEntry.COLUMN_FRIDAY; + } + case 5: { + return PlacesContract.TimeEntry.COLUMN_SATURDAY; + } + case 6: { + return PlacesContract.TimeEntry.COLUMN_SUNDAY; + } + default: { + return null; + } + } + } + + private void setTimesFromHolder(TimeRowHolder holder, Calendar startTime, Calendar endTime) { + String startTimeString = holder.startTime.getText().toString(); + String endTimeString = holder.endTime.getText().toString(); + try { + startTime.setTime(DISPLAY_DATE_FORMAT.parse(startTimeString)); + endTime.setTime(DISPLAY_DATE_FORMAT.parse(endTimeString)); + } catch (ParseException e) { + e.printStackTrace(); + } + } + + private boolean[] getDayArr(TimeRowHolder holder) { + boolean[] isAlarmSetOnDay = new boolean[7]; + for (int i = 0; i < 7; i++) { + isAlarmSetOnDay[i] = holder.days[i].isChecked(); + } + return isAlarmSetOnDay; + } + +} diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/data/PlacesContentProvider.java b/app/src/main/java/com/mdg/droiders/samagra/shush/data/PlacesContentProvider.java index 4a8cb37..54bb908 100644 --- a/app/src/main/java/com/mdg/droiders/samagra/shush/data/PlacesContentProvider.java +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/data/PlacesContentProvider.java @@ -102,7 +102,7 @@ public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable S String id = uri.getPathSegments().get(1); retCursor = db.query(PlacesContract.TimeEntry.TABLE_NAME, projection, - "id =?", + "_id =?", new String[]{id}, null, null, @@ -203,14 +203,14 @@ public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strin // Get the place ID from the URI path String id = uri.getPathSegments().get(1); // Use selections/selectionArgs to filter for this ID - deletedRows = db.delete(PlacesContract.PlaceEntry.TABLE_NAME,"id=?",new String[]{id}); + deletedRows = db.delete(PlacesContract.PlaceEntry.TABLE_NAME,"_id=?",new String[]{id}); break; // Handle the single item case, recognized by the ID included in the URI path case SINGLE_TIME_WITH_ID: // Get the place ID from the URI path String timeId = uri.getPathSegments().get(1); // Use selections/selectionArgs to filter for this ID - deletedRows = db.delete(PlacesContract.TimeEntry.TABLE_NAME,"id=?", new String[]{timeId}); + deletedRows = db.delete(PlacesContract.TimeEntry.TABLE_NAME,"_id=?", new String[]{timeId}); break; default: throw new UnsupportedOperationException("Invalid uri: "+ uri); @@ -254,7 +254,7 @@ public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Null // Get the place ID from the URI path id = uri.getPathSegments().get(1); // Use selections/selectionArgs to filter for this ID - affectedRows = db.update(PlacesContract.TimeEntry.TABLE_NAME,contentValues,"id=?", + affectedRows = db.update(PlacesContract.TimeEntry.TABLE_NAME,contentValues,"_id=?", new String[]{id}); break; // Default exception diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/fragments/TimePickerDialogFragment.java b/app/src/main/java/com/mdg/droiders/samagra/shush/fragments/TimePickerDialogFragment.java new file mode 100644 index 0000000..4b4c8bc --- /dev/null +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/fragments/TimePickerDialogFragment.java @@ -0,0 +1,63 @@ +package com.mdg.droiders.samagra.shush.fragments; + +import android.app.Dialog; +import android.app.TimePickerDialog; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.text.format.DateFormat; +import android.widget.TimePicker; + +import java.util.Calendar; + +/** + * Created by rohan on 19/8/17. + * A fragment that displays a time picker dialog window, + * floating on top of its activity's window. + */ +public class TimePickerDialogFragment extends DialogFragment + implements TimePickerDialog.OnTimeSetListener { + + public interface TimeSetCallback { + void onTimeSet(int hour, int minute); + } + + private TimeSetCallback mCallback; + private Integer hourOfDay; + private Integer minutes; + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + Calendar rightNow = Calendar.getInstance(); + if (hourOfDay == null) { + hourOfDay = rightNow.get(Calendar.HOUR_OF_DAY); + } + if (minutes == null) { + minutes = rightNow.get(Calendar.MINUTE); + } + + return new TimePickerDialog(getActivity(), this, + hourOfDay, + minutes, + DateFormat.is24HourFormat(getActivity())); + } + + @Override + public void onTimeSet(TimePicker timePicker, int hourOfDay, int minute) { + if (mCallback != null) { + mCallback.onTimeSet(hourOfDay, minute); + } + } + + public void setTimeSetCallback(TimeSetCallback mCallback) { + this.mCallback = mCallback; + } + + public void setTime(Calendar displayTime) { + hourOfDay = displayTime.get(Calendar.HOUR_OF_DAY); + minutes = displayTime.get(Calendar.MINUTE); + } + +} diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/interfaces/TimeAdapterNotifier.java b/app/src/main/java/com/mdg/droiders/samagra/shush/interfaces/TimeAdapterNotifier.java new file mode 100644 index 0000000..6387449 --- /dev/null +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/interfaces/TimeAdapterNotifier.java @@ -0,0 +1,39 @@ +package com.mdg.droiders.samagra.shush.interfaces; + +import com.mdg.droiders.samagra.shush.viewholders.TimeRowHolder; + +/** + * Created by rohan on 27/11/17. + * This interface acts as a communication link between {@link TimeRowHolder} and + * {@link com.mdg.droiders.samagra.shush.adapters.TimeListAdapter TimeListAdapter}. + */ +public interface TimeAdapterNotifier { + + /** + * Updates db and reschedules alarm if startTime or endTime changes. + * + * @param holder The holder instance that is currently bound to the row whose + * time is changed. + */ + void notifyTimeChanged(TimeRowHolder holder); + + /** + * Updates db and sets alarm for the corresponding day. + * + * @param holder The holder instance that is currently bound to the row whose + * alarm is set. + * @param day The day for which alarm is set. The day is zero indexed and starts from monday. + */ + void notifyDayAlarmSet(TimeRowHolder holder, int day); + + /** + * Updates db and cancels alarm for the corresponding day. + * + * @param holder The holder instance that is currently bound to the row whose + * alarm is cancelled. + * @param day The day for which alarm is cancelled. The day is zero indexed + * and starts from monday. + */ + void notifyDayAlarmCancelled(TimeRowHolder holder, int day); + +} diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/receivers/AlarmBroadcastReceiver.java b/app/src/main/java/com/mdg/droiders/samagra/shush/receivers/AlarmBroadcastReceiver.java new file mode 100644 index 0000000..9209830 --- /dev/null +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/receivers/AlarmBroadcastReceiver.java @@ -0,0 +1,57 @@ +package com.mdg.droiders.samagra.shush.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; + +import com.mdg.droiders.samagra.shush.AlarmScheduler; +import com.mdg.droiders.samagra.shush.R; +import com.mdg.droiders.samagra.shush.utils.RingerUtils; + +import java.util.Calendar; + +/** + * Created by rohan on 12/8/17. + *
+ * Broadcast Receiver that receives a broadcast from the alarm manager to silence/un-silence the phone. + */ +public class AlarmBroadcastReceiver extends BroadcastReceiver { + + private static final String LOG_TAG = "Samagra/BR/"; + + @Override + public void onReceive(Context context, Intent intent) { + + // The phone will be shushed(silenced) if the boolean shush is true + boolean shush = intent.getBooleanExtra( + context.getString(R.string.alarm_intent_boolean_extra_key), false); + // The timeInMillis at which the alarm is triggered + // Used to trigger an alarm for next week at the same time. + long timeInMillis = intent.getLongExtra( + context.getString(R.string.alarm_intent_long_extra_key), -1); + // The alarm id used in pending intent + int alarmID = intent.getIntExtra( + context.getString(R.string.alarm_intent_int_extra_key), -1); + + if (timeInMillis == -1 || alarmID == -1){ + return; + } + + if (shush) { + RingerUtils.setRingerMode(context, AudioManager.RINGER_MODE_SILENT); + RingerUtils.sendNotification(context, true); + } else { + RingerUtils.setRingerMode(context, AudioManager.RINGER_MODE_NORMAL); + RingerUtils.sendNotification(context, false); + } + + Calendar nextWeek = Calendar.getInstance(); + nextWeek.setTimeInMillis(timeInMillis); + nextWeek.add(Calendar.DAY_OF_MONTH, 7); + + // Schedule an alarm for next week + new AlarmScheduler(context).setAlarm(nextWeek.getTimeInMillis(), alarmID, shush); + + } +} diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/receivers/GeofenceBroadcastReceiver.java b/app/src/main/java/com/mdg/droiders/samagra/shush/receivers/GeofenceBroadcastReceiver.java new file mode 100644 index 0000000..1dec6a3 --- /dev/null +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/receivers/GeofenceBroadcastReceiver.java @@ -0,0 +1,52 @@ +package com.mdg.droiders.samagra.shush.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.util.Log; + +import com.google.android.gms.location.Geofence; +import com.google.android.gms.location.GeofencingEvent; +import com.mdg.droiders.samagra.shush.utils.RingerUtils; + +public class GeofenceBroadcastReceiver extends BroadcastReceiver { + + private static final String LOG_TAG = GeofenceBroadcastReceiver.class.getName(); + + /*** + * Handles the Broadcast message sent when the Geofence Transition is triggered + * Careful here though, this is running on the main thread so make sure you start an AsyncTask for + * anything that takes longer than say 10 second to run + * + * @param context + * @param intent + */ + @Override + public void onReceive(Context context, Intent intent) { + //get the geofencing event sent from the intent + GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); + + if (geofencingEvent.hasError()) { + Log.e(LOG_TAG, String.format("Error Code : %s", geofencingEvent.getErrorCode())); + return; + } + + //get the transition type + int geoFenceTransition = geofencingEvent.getGeofenceTransition(); + + //Check which transition type has triggered the event & Send the notification + if (geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) { + RingerUtils.setRingerMode(context, AudioManager.RINGER_MODE_SILENT); + RingerUtils.sendNotification(context, true); + } else if (geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) { + RingerUtils.setRingerMode(context, AudioManager.RINGER_MODE_NORMAL); + RingerUtils.sendNotification(context, false); + } else { + Log.e(LOG_TAG, String.format("Unknown Transition , %d", geoFenceTransition)); + return; + } + + } + +} diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/utils/RingerUtils.java b/app/src/main/java/com/mdg/droiders/samagra/shush/utils/RingerUtils.java index cd34a2b..7c3163d 100644 --- a/app/src/main/java/com/mdg/droiders/samagra/shush/utils/RingerUtils.java +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/utils/RingerUtils.java @@ -1,9 +1,18 @@ package com.mdg.droiders.samagra.shush.utils; +import android.app.Notification; import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.TaskStackBuilder; import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; import android.media.AudioManager; import android.os.Build; +import android.support.v4.app.NotificationCompat; + +import com.mdg.droiders.samagra.shush.R; +import com.mdg.droiders.samagra.shush.activities.MainActivity; /** * Created by rohan on 13/8/17. @@ -18,13 +27,70 @@ public class RingerUtils { * @param mode The desired mode to switch device to, can be AudioManager.RINGER_MODE_SILENT or * AudioManager.RINGER_MODE_NORMAL */ - public static void setRingerMode(Context context, int mode){ + public static void setRingerMode(Context context, int mode) { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - if (Build.VERSION.SDK_INT<24 || (Build.VERSION.SDK_INT>=24 && !notificationManager.isNotificationPolicyAccessGranted())){ + if (Build.VERSION.SDK_INT < 24 || (Build.VERSION.SDK_INT >= 24 && notificationManager.isNotificationPolicyAccessGranted())) { AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); audioManager.setRingerMode(mode); } } + /** + * Posts a notification in the notification bar. + * Uses different icon drawables for different transition types. + * If the user clicks the notification, control goes to the MainActivity + * + * @param context The calling context for building a task stack + * @param shouldShush Boolean indicating whether the notification is created + * because the phone is shushed or because it is un-shushed. + */ + public static void sendNotification(Context context, boolean shouldShush) { + //create an explicit content intent that starts the main activity + Intent notificationIntent = new Intent(context, MainActivity.class); + + //Construct a task stack + TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); + + //Adding mainActivity to the task stack as the parent task + stackBuilder.addParentStack(MainActivity.class); + + //Push the content intent onto the stack + stackBuilder.addNextIntent(notificationIntent); + + //Get a pending intent consisting the entire backstack + PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); + + //Get a notification builder + NotificationCompat.Builder builder = new NotificationCompat.Builder(context); + + //Check the transition type to display the relevant icon image + + if (shouldShush) { + builder.setSmallIcon(R.drawable.ic_volume_off_white_24dp) + .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), + R.drawable.ic_volume_off_white_24dp)) + .setContentTitle(context.getString(R.string.silent_mode_activated)); + } else { + builder.setSmallIcon(R.drawable.ic_volume_up_white_24dp) + .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), + R.drawable.ic_volume_up_white_24dp)) + .setContentTitle(context.getString(R.string.back_to_normal)); + } + + // Continue building the notification + builder.setContentText(context.getString(R.string.touch_to_relaunch)); + builder.setContentIntent(notificationPendingIntent); + + //Auto remove the notification if the user touches it + builder.setAutoCancel(true); + + // Notification will be shown with light, vibration and default sound. + builder.setDefaults(Notification.DEFAULT_ALL); + + NotificationManager notificationManager = (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + + notificationManager.notify(0, builder.build()); + } } diff --git a/app/src/main/java/com/mdg/droiders/samagra/shush/viewholders/TimeRowHolder.java b/app/src/main/java/com/mdg/droiders/samagra/shush/viewholders/TimeRowHolder.java new file mode 100644 index 0000000..3184d87 --- /dev/null +++ b/app/src/main/java/com/mdg/droiders/samagra/shush/viewholders/TimeRowHolder.java @@ -0,0 +1,112 @@ +package com.mdg.droiders.samagra.shush.viewholders; + +import android.content.Context; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.Switch; +import android.widget.TextView; + +import com.mdg.droiders.samagra.shush.R; +import com.mdg.droiders.samagra.shush.adapters.TimeListAdapter; +import com.mdg.droiders.samagra.shush.fragments.TimePickerDialogFragment; +import com.mdg.droiders.samagra.shush.interfaces.TimeAdapterNotifier; + +import java.text.ParseException; +import java.util.Calendar; + +/** + * Created by rohan on 27/11/17. + * The {@link RecyclerView.ViewHolder ViewHolder} for the row which + * shows if a shush alarm is scheduled in the app. + */ +public class TimeRowHolder extends RecyclerView.ViewHolder { + + private static final String DIALOG_TIME_TAG = "time_picker_dialog_tag"; + + public int id; + public View itemView; + public TextView startTime; + public TextView endTime; + public Switch enableSwitch; + public CheckBox[] days; + + /** + * Listeners attached to the holder will only react to events + * if the a view is bound to the current holder. + */ + public boolean isBound; + + private TimePickerDialogFragment timePickerDialog; + + public TimeRowHolder(View itemView, final TimeAdapterNotifier notifier, + final Context mContext) { + super(itemView); + this.itemView = itemView; + id = -1; + days = new CheckBox[]{ + itemView.findViewById(R.id.monday), + itemView.findViewById(R.id.tuesday), + itemView.findViewById(R.id.wednesday), + itemView.findViewById(R.id.thursday), + itemView.findViewById(R.id.friday), + itemView.findViewById(R.id.saturday), + itemView.findViewById(R.id.sunday) + }; + startTime = itemView.findViewById(R.id.start_time); + endTime = itemView.findViewById(R.id.end_time); + enableSwitch = itemView.findViewById(R.id.enable_switch); + timePickerDialog = new TimePickerDialogFragment(); + View.OnClickListener timeChangeListener = new View.OnClickListener() { + @Override + public void onClick(final View view) { + if (!isBound) { + return; + } + timePickerDialog.setTimeSetCallback(new TimePickerDialogFragment.TimeSetCallback() { + @Override + public void onTimeSet(int hour, int minute) { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + ((TextView) view).setText( + TimeListAdapter.DISPLAY_DATE_FORMAT.format(calendar.getTime())); + notifier.notifyTimeChanged(TimeRowHolder.this); + } + }); + Calendar displayedTime = Calendar.getInstance(); + try { + displayedTime.setTime( + TimeListAdapter.DISPLAY_DATE_FORMAT.parse(((TextView) view).getText().toString())); + } catch (ParseException e) { + e.printStackTrace(); + } + timePickerDialog.setTime(displayedTime); + timePickerDialog.show( + ((AppCompatActivity) mContext).getSupportFragmentManager(), + DIALOG_TIME_TAG + ); + } + }; + startTime.setOnClickListener(timeChangeListener); + endTime.setOnClickListener(timeChangeListener); + for (int i = 0; i < 7; i++) { + final int finalI = i; + days[i].setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + if (!isBound) { + return; + } + if (checked) { + notifier.notifyDayAlarmSet(TimeRowHolder.this, finalI); + } else { + notifier.notifyDayAlarmCancelled(TimeRowHolder.this, finalI); + } + } + }); + } + } +} diff --git a/app/src/main/res/drawable/ic_plus.xml b/app/src/main/res/drawable/ic_plus.xml new file mode 100644 index 0000000..d54c706 --- /dev/null +++ b/app/src/main/res/drawable/ic_plus.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 541773e..d274414 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context="com.mdg.droiders.samagra.shush.MainActivity"> + tools:context="com.mdg.droiders.samagra.shush.activities.MainActivity"> +