Setup RingtoneService
This commit is contained in:
parent
7d4b0c0dff
commit
37e7d3dd3b
@ -30,4 +30,6 @@ dependencies {
|
||||
apt 'com.google.auto.value:auto-value:1.2'
|
||||
compile 'com.android.support:appcompat-v7:23.2.1'
|
||||
compile 'com.android.support:design:23.2.1'
|
||||
compile 'com.android.support:support-v4:23.2.1'
|
||||
compile 'com.android.support:recyclerview-v7:23.2.1'
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.philliphsu.clock;
|
||||
package com.philliphsu.clock2;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.philliphsu.clock"
|
||||
<manifest package="com.philliphsu.clock2"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
@ -9,7 +9,7 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:name="com.philliphsu.clock2.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
<intent-filter>
|
||||
@ -18,6 +18,18 @@
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.philliphsu.clock2.ringtone.RingtoneActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:label="@string/title_activity_ringtone"
|
||||
android:theme="@style/FullscreenTheme">
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name="com.philliphsu.clock2.ringtone.RingtoneService"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@ -1,4 +1,4 @@
|
||||
package com.philliphsu.clock;
|
||||
package com.philliphsu.clock2;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
|
||||
@ -1,24 +1,28 @@
|
||||
package com.philliphsu.clock;
|
||||
package com.philliphsu.clock2;
|
||||
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.media.RingtoneManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.philliphsu.clock2.ringtone.RingtoneActivity;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
/**
|
||||
@ -58,8 +62,17 @@ public class MainActivity extends AppCompatActivity {
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", null).show();
|
||||
scheduleAlarm();
|
||||
Snackbar.make(view, "Alarm set for 1 minute from now", Snackbar.LENGTH_LONG)
|
||||
.setAction("Dismiss", new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
|
||||
PendingIntent pi = alarmIntent();
|
||||
am.cancel(pi);
|
||||
pi.cancel();
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
});
|
||||
|
||||
@ -159,4 +172,25 @@ public class MainActivity extends AppCompatActivity {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleAlarm() {
|
||||
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
|
||||
// If there is already an alarm for this Intent scheduled (with the equality of two
|
||||
// intents being defined by filterEquals(Intent)), then it will be removed and replaced
|
||||
// by this one. For most of our uses, the relevant criteria for equality will be the
|
||||
// action, the data, and the class (component). Although not documented, the request code
|
||||
// of a PendingIntent is also considered to determine equality of two intents.
|
||||
// todo: get alarm's ring time
|
||||
am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 60000, alarmIntent());
|
||||
}
|
||||
|
||||
private PendingIntent alarmIntent() {
|
||||
// TODO: Use appropriate subclass instead
|
||||
Intent intent = new Intent(this, RingtoneActivity.class)
|
||||
.setData(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM));
|
||||
// TODO: Use unique request codes per alarm.
|
||||
// If a PendingIntent with this request code already exists, then we are likely modifying
|
||||
// an alarm, so we should cancel the existing intent.
|
||||
return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.philliphsu.clock2.alarms;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.alarms.dummy.DummyContent.DummyItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link RecyclerView.Adapter} that can display a {@link DummyItem} and makes a call to the
|
||||
* specified {@link AlarmsFragment.OnListFragmentInteractionListener}.
|
||||
* TODO: Replace the implementation with code for your data type.
|
||||
*/
|
||||
public class AlarmsAdapter extends RecyclerView.Adapter<AlarmsAdapter.ViewHolder> {
|
||||
|
||||
private final List<DummyItem> mValues;
|
||||
private final AlarmsFragment.OnListFragmentInteractionListener mListener;
|
||||
|
||||
public AlarmsAdapter(List<DummyItem> items, AlarmsFragment.OnListFragmentInteractionListener listener) {
|
||||
mValues = items;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.fragment_alarms, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||
holder.mItem = mValues.get(position);
|
||||
holder.mIdView.setText(mValues.get(position).id);
|
||||
holder.mContentView.setText(mValues.get(position).content);
|
||||
|
||||
holder.mView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (null != mListener) {
|
||||
// Notify the active callbacks interface (the activity, if the
|
||||
// fragment is attached to one) that an item has been selected.
|
||||
mListener.onListFragmentInteraction(holder.mItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final View mView;
|
||||
public final TextView mIdView;
|
||||
public final TextView mContentView;
|
||||
public DummyItem mItem;
|
||||
|
||||
public ViewHolder(View view) {
|
||||
super(view);
|
||||
mView = view;
|
||||
mIdView = (TextView) view.findViewById(R.id.id);
|
||||
mContentView = (TextView) view.findViewById(R.id.content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
package com.philliphsu.clock2.alarms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.alarms.dummy.DummyContent;
|
||||
import com.philliphsu.clock2.alarms.dummy.DummyContent.DummyItem;
|
||||
|
||||
/**
|
||||
* A fragment representing a list of Items.
|
||||
* <p/>
|
||||
* Activities containing this fragment MUST implement the {@link OnListFragmentInteractionListener}
|
||||
* interface.
|
||||
*/
|
||||
public class AlarmsFragment extends Fragment {
|
||||
|
||||
// TODO: Customize parameter argument names
|
||||
private static final String ARG_COLUMN_COUNT = "column-count";
|
||||
// TODO: Customize parameters
|
||||
private int mColumnCount = 1;
|
||||
private OnListFragmentInteractionListener mListener;
|
||||
|
||||
/**
|
||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||
* fragment (e.g. upon screen orientation changes).
|
||||
*/
|
||||
public AlarmsFragment() {
|
||||
}
|
||||
|
||||
// TODO: Customize parameter initialization
|
||||
@SuppressWarnings("unused")
|
||||
public static AlarmsFragment newInstance(int columnCount) {
|
||||
AlarmsFragment fragment = new AlarmsFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_COLUMN_COUNT, columnCount);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (getArguments() != null) {
|
||||
mColumnCount = getArguments().getInt(ARG_COLUMN_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_alarms_list, container, false);
|
||||
|
||||
// Set the adapter
|
||||
if (view instanceof RecyclerView) {
|
||||
Context context = view.getContext();
|
||||
RecyclerView recyclerView = (RecyclerView) view;
|
||||
if (mColumnCount <= 1) {
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(context));
|
||||
} else {
|
||||
recyclerView.setLayoutManager(new GridLayoutManager(context, mColumnCount));
|
||||
}
|
||||
recyclerView.setAdapter(new AlarmsAdapter(DummyContent.ITEMS, mListener));
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof OnListFragmentInteractionListener) {
|
||||
mListener = (OnListFragmentInteractionListener) context;
|
||||
} else {
|
||||
throw new RuntimeException(context.toString()
|
||||
+ " must implement OnListFragmentInteractionListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface must be implemented by activities that contain this
|
||||
* fragment to allow an interaction in this fragment to be communicated
|
||||
* to the activity and potentially other fragments contained in that
|
||||
* activity.
|
||||
* <p/>
|
||||
* See the Android Training lesson <a href=
|
||||
* "http://developer.android.com/training/basics/fragments/communicating.html"
|
||||
* >Communicating with Other Fragments</a> for more information.
|
||||
*/
|
||||
public interface OnListFragmentInteractionListener {
|
||||
// TODO: Update argument type and name
|
||||
void onListFragmentInteraction(DummyItem item);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
package com.philliphsu.clock2.alarms.dummy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class for providing sample content for user interfaces created by
|
||||
* Android template wizards.
|
||||
* <p/>
|
||||
* TODO: Replace all uses of this class before publishing your app.
|
||||
*/
|
||||
public class DummyContent {
|
||||
|
||||
/**
|
||||
* An array of sample (dummy) items.
|
||||
*/
|
||||
public static final List<DummyItem> ITEMS = new ArrayList<DummyItem>();
|
||||
|
||||
/**
|
||||
* A map of sample (dummy) items, by ID.
|
||||
*/
|
||||
public static final Map<String, DummyItem> ITEM_MAP = new HashMap<String, DummyItem>();
|
||||
|
||||
private static final int COUNT = 25;
|
||||
|
||||
static {
|
||||
// Add some sample items.
|
||||
for (int i = 1; i <= COUNT; i++) {
|
||||
addItem(createDummyItem(i));
|
||||
}
|
||||
}
|
||||
|
||||
private static void addItem(DummyItem item) {
|
||||
ITEMS.add(item);
|
||||
ITEM_MAP.put(item.id, item);
|
||||
}
|
||||
|
||||
private static DummyItem createDummyItem(int position) {
|
||||
return new DummyItem(String.valueOf(position), "Item " + position, makeDetails(position));
|
||||
}
|
||||
|
||||
private static String makeDetails(int position) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("Details about Item: ").append(position);
|
||||
for (int i = 0; i < position; i++) {
|
||||
builder.append("\nMore details information here.");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy item representing a piece of content.
|
||||
*/
|
||||
public static class DummyItem {
|
||||
public final String id;
|
||||
public final String content;
|
||||
public final String details;
|
||||
|
||||
public DummyItem(String id, String content, String details) {
|
||||
this.id = id;
|
||||
this.content = content;
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,175 @@
|
||||
package com.philliphsu.clock2.ringtone;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.philliphsu.clock2.R;
|
||||
|
||||
import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* An example full-screen activity that shows and hides the system UI (i.e.
|
||||
* status bar and navigation/system bar) with user interaction.
|
||||
*
|
||||
* TODO: Make this abstract and make appropriate subclasses for Alarms and Timers.
|
||||
* TODO: Use this together with RingtoneService.
|
||||
*/
|
||||
public class RingtoneActivity extends AppCompatActivity {
|
||||
/**
|
||||
* Whether or not the system UI should be auto-hidden after
|
||||
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
|
||||
*/
|
||||
private static final boolean AUTO_HIDE = true;
|
||||
|
||||
/**
|
||||
* If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
|
||||
* user interaction before hiding the system UI.
|
||||
*/
|
||||
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
|
||||
|
||||
/**
|
||||
* Some older devices needs a small delay between UI widget updates
|
||||
* and a change of the status and navigation bar.
|
||||
*/
|
||||
private static final int UI_ANIMATION_DELAY = 300;
|
||||
private final Handler mHideHandler = new Handler();
|
||||
private View mContentView;
|
||||
private final Runnable mHidePart2Runnable = new Runnable() {
|
||||
@SuppressLint("InlinedApi")
|
||||
@Override
|
||||
public void run() {
|
||||
// Delayed removal of status and navigation bar
|
||||
|
||||
// Note that some of these constants are new as of API 16 (Jelly Bean)
|
||||
// and API 19 (KitKat). It is safe to use them, as they are inlined
|
||||
// at compile-time and do nothing on earlier devices.
|
||||
mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
|
||||
}
|
||||
};
|
||||
private View mControlsView;
|
||||
private final Runnable mShowPart2Runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Delayed display of UI elements
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.show();
|
||||
}
|
||||
mControlsView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
};
|
||||
private boolean mVisible;
|
||||
private final Runnable mHideRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Touch listener to use for in-layout UI controls to delay hiding the
|
||||
* system UI. This is to prevent the jarring behavior of controls going away
|
||||
* while interacting with activity UI.
|
||||
*/
|
||||
private final View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||
if (AUTO_HIDE) {
|
||||
delayedHide(AUTO_HIDE_DELAY_MILLIS);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_ringtone);
|
||||
Uri ringtone = checkNotNull(getIntent().getData());
|
||||
Intent intent = new Intent(this, RingtoneService.class).setData(ringtone);
|
||||
startService(intent);
|
||||
|
||||
mVisible = true;
|
||||
mControlsView = findViewById(R.id.fullscreen_content_controls);
|
||||
mContentView = findViewById(R.id.fullscreen_content);
|
||||
|
||||
|
||||
// Set up the user interaction to manually show or hide the system UI.
|
||||
mContentView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
toggle();
|
||||
}
|
||||
});
|
||||
|
||||
// Upon interacting with UI controls, delay any scheduled hide()
|
||||
// operations to prevent the jarring behavior of controls going away
|
||||
// while interacting with the UI.
|
||||
findViewById(R.id.dummy_button).setOnTouchListener(mDelayHideTouchListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
|
||||
// Trigger the initial hide() shortly after the activity has been
|
||||
// created, to briefly hint to the user that UI controls
|
||||
// are available.
|
||||
delayedHide(100);
|
||||
}
|
||||
|
||||
private void toggle() {
|
||||
if (mVisible) {
|
||||
hide();
|
||||
} else {
|
||||
show();
|
||||
}
|
||||
}
|
||||
|
||||
private void hide() {
|
||||
// Hide UI first
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.hide();
|
||||
}
|
||||
mControlsView.setVisibility(View.GONE);
|
||||
mVisible = false;
|
||||
|
||||
// Schedule a runnable to remove the status and navigation bar after a delay
|
||||
mHideHandler.removeCallbacks(mShowPart2Runnable);
|
||||
mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private void show() {
|
||||
// Show the system bar
|
||||
mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
||||
mVisible = true;
|
||||
|
||||
// Schedule a runnable to display UI elements after a delay
|
||||
mHideHandler.removeCallbacks(mHidePart2Runnable);
|
||||
mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a call to hide() in [delay] milliseconds, canceling any
|
||||
* previously scheduled calls.
|
||||
*/
|
||||
private void delayedHide(int delayMillis) {
|
||||
mHideHandler.removeCallbacks(mHideRunnable);
|
||||
mHideHandler.postDelayed(mHideRunnable, delayMillis);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
package com.philliphsu.clock2.ringtone;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.philliphsu.clock2.R;
|
||||
|
||||
import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Runs in the foreground. While it can still be killed by the system, it stays alive significantly
|
||||
* longer than if it does not run in the foreground. The longevity should be sufficient for practical
|
||||
* use. In fact, if the app is used properly, longevity should be a non-issue; realistically, the lifetime
|
||||
* of the RingtoneService will be tied to that of its RingtoneActivity because users are not likely to
|
||||
* navigate away from the Activity without making an action. But if they do accidentally navigate away,
|
||||
* they have plenty of time to make the desired action via the notification.
|
||||
*/
|
||||
public class RingtoneService extends Service {
|
||||
private static final String TAG = "RingtoneService";
|
||||
|
||||
private AudioManager mAudioManager;
|
||||
private Ringtone mRingtone;
|
||||
private boolean mAutoSilenced = false;
|
||||
private final Handler mSilenceHandler = new Handler();
|
||||
private final Runnable mSilenceRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mAutoSilenced = true;
|
||||
stopSelf();
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Apply the setting for "Silence after" here by using an AlarmManager to
|
||||
// schedule an alarm in the future to stop this service, and also update the foreground
|
||||
// notification to say "alarm missed" in the case of Alarms or "timer expired" for Timers.
|
||||
// If Alarms and Timers will have distinct settings for this, then consider doing this
|
||||
// operation in the respective subclass of this service.
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (mAudioManager == null && mRingtone == null) {
|
||||
Uri ringtone = checkNotNull(intent.getData());
|
||||
// TODO: The below call requires a notification, and there is no way to provide one suitable
|
||||
// for both Alarms and Timers. Consider making this class abstract, and have subclasses
|
||||
// implement an abstract method that calls startForeground(). You would then call that
|
||||
// method here instead.
|
||||
Notification note = new NotificationCompat.Builder(this)
|
||||
// Required contents
|
||||
.setSmallIcon(R.mipmap.ic_launcher) // TODO: alarm icon
|
||||
.setContentTitle("Foreground RingtoneService")
|
||||
.setContentText("Ringtone is playing in the foreground.")
|
||||
.build();
|
||||
startForeground(R.id.ringtone_service_notification, note); // TOneverDO: Pass 0 as the first argument
|
||||
|
||||
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
// Request audio focus first, so we don't play our ringtone on top of any
|
||||
// other apps that currently have playback.
|
||||
int result = mAudioManager.requestAudioFocus(
|
||||
null, // Playback will likely be short, so don't worry about listening for focus changes
|
||||
AudioManager.STREAM_ALARM,
|
||||
// Request permanent focus, as ringing could last several minutes
|
||||
AudioManager.AUDIOFOCUS_GAIN);
|
||||
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||
mRingtone = RingtoneManager.getRingtone(this, ringtone);
|
||||
// Deprecated, but the alternative AudioAttributes requires API 21
|
||||
mRingtone.setStreamType(AudioManager.STREAM_ALARM);
|
||||
mRingtone.play();
|
||||
scheduleAutoSilence();
|
||||
}
|
||||
}
|
||||
// If killed while started, don't recreate
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d(TAG, "onDestroy()");
|
||||
mRingtone.stop();
|
||||
mAudioManager.abandonAudioFocus(null); // no listener was set
|
||||
mSilenceHandler.removeCallbacks(mSilenceRunnable);
|
||||
if (mAutoSilenced) {
|
||||
// Post notification that alarm was missed, or timer expired.
|
||||
// TODO: You should probably do this in the appropriate subclass.
|
||||
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
Notification note = new NotificationCompat.Builder(this)
|
||||
.setContentTitle("Missed alarm")
|
||||
.setContentText("Regular alarm time here")
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.build();
|
||||
nm.notify("tag", 0, note);
|
||||
}
|
||||
stopForeground(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null; // Binding to this service is not supported
|
||||
}
|
||||
|
||||
private void scheduleAutoSilence() {
|
||||
// TODO: Read prefs
|
||||
//SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
int minutes = 2; /*Integer.parseInt(pref.getString(
|
||||
getString(R.string.key_silence_after),
|
||||
"15"));*/
|
||||
mSilenceHandler.postDelayed(mSilenceRunnable, minutes * 60000);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package com.philliphsu.clock2.util;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 5/28/2016.
|
||||
*/
|
||||
public final class Preconditions {
|
||||
private Preconditions() {}
|
||||
|
||||
public static <T> T checkNotNull(T obj) {
|
||||
if (null == obj)
|
||||
throw new NullPointerException();
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@ -1,42 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout android:id="@+id/main_content"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="com.philliphsu.clock.MainActivity">
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="com.philliphsu.clock2.MainActivity">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/appbar_padding_top"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay">
|
||||
|
||||
</android.support.v7.widget.Toolbar>
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay">
|
||||
|
||||
<android.support.design.widget.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
</android.support.v7.widget.Toolbar>
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
@ -46,4 +32,4 @@
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
android:src="@android:drawable/ic_dialog_email"/>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</merge>
|
||||
|
||||
50
app/src/main/res/layout/activity_ringtone.xml
Normal file
50
app/src/main/res/layout/activity_ringtone.xml
Normal file
@ -0,0 +1,50 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#0099cc"
|
||||
tools:context=".ringtone.RingtoneActivity">
|
||||
|
||||
<!-- The primary full-screen view. This can be replaced with whatever view
|
||||
is needed to present your content, e.g. VideoView, SurfaceView,
|
||||
TextureView, etc. -->
|
||||
<TextView
|
||||
android:id="@+id/fullscreen_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:keepScreenOn="true"
|
||||
android:text="@string/dummy_content"
|
||||
android:textColor="#33b5e5"
|
||||
android:textSize="50sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<!-- This FrameLayout insets its children based on system windows using
|
||||
android:fitsSystemWindows. -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/fullscreen_content_controls"
|
||||
style="?metaButtonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:background="@color/black_overlay"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/dummy_button"
|
||||
style="?metaButtonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/dummy_button"/>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
||||
20
app/src/main/res/layout/fragment_alarms.xml
Normal file
20
app/src/main/res/layout/fragment_alarms.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/text_margin"
|
||||
android:textAppearance="?attr/textAppearanceListItem"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/text_margin"
|
||||
android:textAppearance="?attr/textAppearanceListItem"/>
|
||||
</LinearLayout>
|
||||
14
app/src/main/res/layout/fragment_alarms_list.xml
Normal file
14
app/src/main/res/layout/fragment_alarms_list.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/list"
|
||||
android:name="com.philliphsu.clock.alarms.AlarmsFragment"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:context="com.philliphsu.clock2.alarms.AlarmsFragment"
|
||||
tools:listitem="@layout/fragment_alarms"/>
|
||||
@ -6,7 +6,7 @@
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
tools:context="com.philliphsu.clock.MainActivity$PlaceholderFragment">
|
||||
tools:context="com.philliphsu.clock2.MainActivity$PlaceholderFragment">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/section_label"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="com.philliphsu.clock.MainActivity">
|
||||
tools:context="com.philliphsu.clock2.MainActivity">
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="100"
|
||||
|
||||
12
app/src/main/res/values/attrs.xml
Normal file
12
app/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<resources>
|
||||
|
||||
<!-- Declare custom theme attributes that allow changing which styles are
|
||||
used for button bars depending on the API level.
|
||||
?android:attr/buttonBarStyle is new as of API 11 so this is
|
||||
necessary to support previous API levels. -->
|
||||
<declare-styleable name="ButtonBarContainerTheme">
|
||||
<attr name="metaButtonBarStyle" format="reference"/>
|
||||
<attr name="metaButtonBarButtonStyle" format="reference"/>
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
@ -3,4 +3,6 @@
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
|
||||
<color name="black_overlay">#66000000</color>
|
||||
</resources>
|
||||
|
||||
@ -4,4 +4,5 @@
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
<dimen name="appbar_padding_top">8dp</dimen>
|
||||
<dimen name="text_margin">16dp</dimen>
|
||||
</resources>
|
||||
|
||||
4
app/src/main/res/values/ids.xml
Normal file
4
app/src/main/res/values/ids.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item type="id" name="ringtone_service_notification"/>
|
||||
</resources>
|
||||
@ -1,5 +1,9 @@
|
||||
<resources>
|
||||
<string name="app_name">Clock+</string>
|
||||
<string name="app_name">Clock+2</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="section_format">Hello World from section: %1$d</string>
|
||||
|
||||
<string name="title_activity_ringtone">Clock+</string>
|
||||
<string name="dummy_button">Dummy Button</string>
|
||||
<string name="dummy_content">DUMMY\nCONTENT</string>
|
||||
</resources>
|
||||
|
||||
@ -17,4 +17,16 @@
|
||||
|
||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
|
||||
|
||||
<style name="FullscreenTheme" parent="AppTheme">
|
||||
<item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
|
||||
<item name="android:windowActionBarOverlay">true</item>
|
||||
<item name="android:windowBackground">@null</item>
|
||||
<item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
|
||||
<item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="FullscreenActionBarStyle" parent="Widget.AppCompat.ActionBar">
|
||||
<item name="android:background">@color/black_overlay</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.philliphsu.clock;
|
||||
package com.philliphsu.clock2;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.philliphsu.clock;
|
||||
package com.philliphsu.clock2;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user