Setup RingtoneService

This commit is contained in:
Phillip Hsu 2016-05-28 18:44:51 -07:00
parent 7d4b0c0dff
commit 37e7d3dd3b
25 changed files with 763 additions and 45 deletions

View File

@ -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'
}

View File

@ -1,4 +1,4 @@
package com.philliphsu.clock;
package com.philliphsu.clock2;
import android.app.Application;
import android.test.ApplicationTestCase;

View File

@ -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>

View File

@ -1,4 +1,4 @@
package com.philliphsu.clock;
package com.philliphsu.clock2;
import com.google.auto.value.AutoValue;

View File

@ -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);
}
}

View File

@ -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() + "'";
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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>

View 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>

View 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>

View 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"/>

View File

@ -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"

View File

@ -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"

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="ringtone_service_notification"/>
</resources>

View File

@ -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>

View File

@ -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>

View File

@ -1,4 +1,4 @@
package com.philliphsu.clock;
package com.philliphsu.clock2;
import org.junit.Test;

View File

@ -1,4 +1,4 @@
package com.philliphsu.clock;
package com.philliphsu.clock2;
import org.junit.Test;