Add two new samples and one new sample category.
- Added MediaBrowserService Android Auto compatible media sample;
- Added MessagingService Android Auto compatible notification sample;
- Renamed package names of both new samples;
- Created a new "notification" category;
- Moved existing notifications samples to the new "notification" category;
Bug: 18323953
Change-Id: Ibdc785ba68b6f9431d61525af5a4456a8598d5ce
diff --git a/build.gradle b/build.gradle
index 9615031..740e531 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,8 +24,8 @@
"ui/actionbarcompat/ActionBarCompat-Styled",
"ui/actionbarcompat/ActionBarCompat-ListPopupMenu",
"ui/actionbarcompat/ActionBarCompat-ShareActionProvider",
-"ui/notifications/BasicNotifications",
-"ui/notifications/CustomNotifications",
+"notification/BasicNotifications",
+"notification/CustomNotifications",
"ui/actionbar/DoneBar",
"ui/graphics/PdfRendererBasic",
"ui/window/BasicImmersiveMode",
@@ -47,6 +47,7 @@
"ui/views/SwipeRefreshLayout/SwipeRefreshLayoutBasic",
"ui/views/SwipeRefreshLayout/SwipeRefreshListFragment",
"ui/views/SwipeRefreshLayout/SwipeRefreshMultipleViews",
+"media/MediaBrowserService",
"media/MediaRouter",
"media/MediaEffects",
"admin/BasicManagedProfile",
@@ -62,7 +63,8 @@
"background/JobScheduler",
"ui/views/RecyclerView",
"ui/views/CardView",
-"ui/notifications/LNotifications",
+"notification/LNotifications",
+"notification/MessagingService",
"ui/DrawableTinting",
"ui/Interpolator",
"media/HdrViewfinder",
diff --git a/ui/notifications/LNotifications/Application/.gitignore b/media/MediaBrowserService/Application/.gitignore
similarity index 100%
copy from ui/notifications/LNotifications/Application/.gitignore
copy to media/MediaBrowserService/Application/.gitignore
diff --git a/ui/notifications/LNotifications/Application/proguard-project.txt b/media/MediaBrowserService/Application/proguard-project.txt
similarity index 100%
copy from ui/notifications/LNotifications/Application/proguard-project.txt
copy to media/MediaBrowserService/Application/proguard-project.txt
diff --git a/media/MediaBrowserService/Application/src/androidTest/java/com/example/android/mediabrowserservice/test/SampleTests.java b/media/MediaBrowserService/Application/src/androidTest/java/com/example/android/mediabrowserservice/test/SampleTests.java
new file mode 100644
index 0000000..7bf34eb
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/androidTest/java/com/example/android/mediabrowserservice/test/SampleTests.java
@@ -0,0 +1,76 @@
+/*
+* Copyright 2013 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+/*
+* Copyright (C) 2014 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.example.android.mediabrowserservice.test;
+
+import com.example.android.mediabrowserservice.*;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+* Tests for MediaBrowserService sample.
+*/
+public class SampleTests extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private MainActivity mTestActivity;
+ private MediaBrowserServiceFragment mTestFragment;
+
+ public SampleTests() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // Starts the activity under test using the default Intent with:
+ // action = {@link Intent#ACTION_MAIN}
+ // flags = {@link Intent#FLAG_ACTIVITY_NEW_TASK}
+ // All other fields are null or empty.
+ mTestActivity = getActivity();
+ mTestFragment = (MediaBrowserServiceFragment)
+ mTestActivity.getSupportFragmentManager().getFragments().get(1);
+ }
+
+ /**
+ * Test if the test fixture has been set up correctly.
+ */
+ public void testPreconditions() {
+ //Try to add a message to add context to your assertions. These messages will be shown if
+ //a tests fails and make it easy to understand why a test failed
+ assertNotNull("mTestActivity is null", mTestActivity);
+ assertNotNull("mTestFragment is null", mTestFragment);
+ }
+
+ /**
+ * Add more tests below.
+ */
+
+}
diff --git a/media/MediaBrowserService/Application/src/main/AndroidManifest.xml b/media/MediaBrowserService/Application/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6d05c27
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/AndroidManifest.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.mediabrowserservice"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+ <uses-sdk
+ android:minSdkVersion="21"
+ android:targetSdkVersion="21" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme">
+
+ <meta-data android:name="com.google.android.gms.car.application"
+ android:resource="@xml/automotive_app_desc"/>
+
+
+ <activity android:name="com.example.android.mediabrowserservice.MusicPlayerActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- (OPTIONAL) use this meta data to indicate which icon should be used in media
+ notifications (for example, when the music changes and the user is
+ looking at another app) -->
+ <meta-data
+ android:name="com.google.android.gms.car.notification.SmallIcon"
+ android:resource="@drawable/ic_notification" />
+
+ <service
+ android:name="com.example.android.mediabrowserservice.MusicService"
+ android:exported="true"
+ >
+ <intent-filter>
+ <action android:name="android.media.browse.MediaBrowserService" />
+ </intent-filter>
+ </service>
+
+ </application>
+
+</manifest>
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/BrowseFragment.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/BrowseFragment.java
new file mode 100644
index 0000000..726ae15
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/BrowseFragment.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.mediabrowserservice;
+
+import android.app.Fragment;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaController;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.example.android.mediabrowserservice.utils.LogHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A Fragment that lists all the various browsable queues available
+ * from a {@link android.service.media.MediaBrowserService}.
+ * <p/>
+ * It uses a {@link MediaBrowser} to connect to the {@link MusicService}. Once connected,
+ * the fragment subscribes to get all the children. All {@link MediaBrowser.MediaItem}'s
+ * that can be browsed are shown in a ListView.
+ */
+public class BrowseFragment extends Fragment {
+
+ private static final String TAG = BrowseFragment.class.getSimpleName();
+
+ public static final String ARG_MEDIA_ID = "media_id";
+
+ public static interface FragmentDataHelper {
+ void onMediaItemSelected(MediaBrowser.MediaItem item);
+ }
+
+ // The mediaId to be used for subscribing for children using the MediaBrowser.
+ private String mMediaId;
+
+ private MediaBrowser mMediaBrowser;
+ private BrowseAdapter mBrowserAdapter;
+
+ private MediaBrowser.SubscriptionCallback mSubscriptionCallback = new MediaBrowser.SubscriptionCallback() {
+
+ @Override
+ public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
+ mBrowserAdapter.clear();
+ mBrowserAdapter.notifyDataSetInvalidated();
+ for (MediaBrowser.MediaItem item : children) {
+ mBrowserAdapter.add(item);
+ }
+ mBrowserAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onError(String id) {
+ Toast.makeText(getActivity(), R.string.error_loading_media,
+ Toast.LENGTH_LONG).show();
+ }
+ };
+
+ private MediaBrowser.ConnectionCallback mConnectionCallback =
+ new MediaBrowser.ConnectionCallback() {
+ @Override
+ public void onConnected() {
+ LogHelper.d(TAG, "onConnected: session token " + mMediaBrowser.getSessionToken());
+
+ if (mMediaId == null) {
+ mMediaId = mMediaBrowser.getRoot();
+ }
+ mMediaBrowser.subscribe(mMediaId, mSubscriptionCallback);
+ if (mMediaBrowser.getSessionToken() == null) {
+ throw new IllegalArgumentException("No Session token");
+ }
+ MediaController mediaController = new MediaController(getActivity(),
+ mMediaBrowser.getSessionToken());
+ getActivity().setMediaController(mediaController);
+ }
+
+ @Override
+ public void onConnectionFailed() {
+ LogHelper.d(TAG, "onConnectionFailed");
+ }
+
+ @Override
+ public void onConnectionSuspended() {
+ LogHelper.d(TAG, "onConnectionSuspended");
+ getActivity().setMediaController(null);
+ }
+ };
+
+ public static BrowseFragment newInstance(String mediaId) {
+ Bundle args = new Bundle();
+ args.putString(ARG_MEDIA_ID, mediaId);
+ BrowseFragment fragment = new BrowseFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_list, container, false);
+
+ mBrowserAdapter = new BrowseAdapter(getActivity());
+
+ View controls = rootView.findViewById(R.id.controls);
+ controls.setVisibility(View.GONE);
+
+ ListView listView = (ListView) rootView.findViewById(R.id.list_view);
+ listView.setAdapter(mBrowserAdapter);
+ listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ MediaBrowser.MediaItem item = mBrowserAdapter.getItem(position);
+ try {
+ FragmentDataHelper listener = (FragmentDataHelper) getActivity();
+ listener.onMediaItemSelected(item);
+ } catch (ClassCastException ex) {
+ Log.e(TAG, "Exception trying to cast to FragmentDataHelper", ex);
+ }
+ }
+ });
+
+ Bundle args = getArguments();
+ mMediaId = args.getString(ARG_MEDIA_ID, null);
+
+ mMediaBrowser = new MediaBrowser(getActivity(),
+ new ComponentName(getActivity(), MusicService.class),
+ mConnectionCallback, null);
+
+ return rootView;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mMediaBrowser.connect();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mMediaBrowser.disconnect();
+ }
+
+ // An adapter for showing the list of browsed MediaItem's
+ private static class BrowseAdapter extends ArrayAdapter<MediaBrowser.MediaItem> {
+
+ public BrowseAdapter(Context context) {
+ super(context, R.layout.media_list_item, new ArrayList<MediaBrowser.MediaItem>());
+ }
+
+ static class ViewHolder {
+ ImageView mImageView;
+ TextView mTitleView;
+ TextView mDescriptionView;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+
+ ViewHolder holder;
+
+ if (convertView == null) {
+ convertView = LayoutInflater.from(getContext())
+ .inflate(R.layout.media_list_item, parent, false);
+ holder = new ViewHolder();
+ holder.mImageView = (ImageView) convertView.findViewById(R.id.play_eq);
+ holder.mImageView.setVisibility(View.GONE);
+ holder.mTitleView = (TextView) convertView.findViewById(R.id.title);
+ holder.mDescriptionView = (TextView) convertView.findViewById(R.id.description);
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
+
+ MediaBrowser.MediaItem item = getItem(position);
+ holder.mTitleView.setText(item.getDescription().getTitle());
+ holder.mDescriptionView.setText(item.getDescription().getDescription());
+ if (item.isPlayable()) {
+ holder.mImageView.setImageDrawable(
+ getContext().getDrawable(R.drawable.ic_play_arrow_white_24dp));
+ holder.mImageView.setVisibility(View.VISIBLE);
+ }
+ return convertView;
+ }
+ }
+}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MediaNotification.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MediaNotification.java
new file mode 100644
index 0000000..7b8631a
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MediaNotification.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2014 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.mediabrowserservice;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.AsyncTask;
+import android.util.LruCache;
+import android.util.SparseArray;
+
+import com.example.android.mediabrowserservice.utils.BitmapHelper;
+import com.example.android.mediabrowserservice.utils.LogHelper;
+
+import java.io.IOException;
+
+/**
+ * Keeps track of a notification and updates it automatically for a given
+ * MediaSession. Maintaining a visible notification (usually) guarantees that the music service
+ * won't be killed during playback.
+ */
+public class MediaNotification extends BroadcastReceiver {
+ private static final String TAG = "MediaNotification";
+
+ private static final int NOTIFICATION_ID = 412;
+
+ public static final String ACTION_PAUSE = "com.example.android.mediabrowserservice.pause";
+ public static final String ACTION_PLAY = "com.example.android.mediabrowserservice.play";
+ public static final String ACTION_PREV = "com.example.android.mediabrowserservice.prev";
+ public static final String ACTION_NEXT = "com.example.android.mediabrowserservice.next";
+
+ private static final int MAX_ALBUM_ART_CACHE_SIZE = 1024*1024;
+
+ private final MusicService mService;
+ private MediaSession.Token mSessionToken;
+ private MediaController mController;
+ private MediaController.TransportControls mTransportControls;
+ private final SparseArray<PendingIntent> mIntents = new SparseArray<PendingIntent>();
+ private final LruCache<String, Bitmap> mAlbumArtCache;
+
+ private PlaybackState mPlaybackState;
+ private MediaMetadata mMetadata;
+
+ private Notification.Builder mNotificationBuilder;
+ private NotificationManager mNotificationManager;
+ private Notification.Action mPlayPauseAction;
+
+ private String mCurrentAlbumArt;
+ private int mNotificationColor;
+
+ private boolean mStarted = false;
+
+ public MediaNotification(MusicService service) {
+ mService = service;
+ updateSessionToken();
+
+ // simple album art cache that holds no more than
+ // MAX_ALBUM_ART_CACHE_SIZE bytes:
+ mAlbumArtCache = new LruCache<String, Bitmap>(MAX_ALBUM_ART_CACHE_SIZE) {
+ @Override
+ protected int sizeOf(String key, Bitmap value) {
+ return value.getByteCount();
+ }
+ };
+
+ mNotificationColor = getNotificationColor();
+
+ mNotificationManager = (NotificationManager) mService
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ String pkg = mService.getPackageName();
+ mIntents.put(R.drawable.ic_pause_white_24dp, PendingIntent.getBroadcast(mService, 100,
+ new Intent(ACTION_PAUSE).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT));
+ mIntents.put(R.drawable.ic_play_arrow_white_24dp, PendingIntent.getBroadcast(mService, 100,
+ new Intent(ACTION_PLAY).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT));
+ mIntents.put(R.drawable.ic_skip_previous_white_24dp, PendingIntent.getBroadcast(mService, 100,
+ new Intent(ACTION_PREV).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT));
+ mIntents.put(R.drawable.ic_skip_next_white_24dp, PendingIntent.getBroadcast(mService, 100,
+ new Intent(ACTION_NEXT).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT));
+ }
+
+ protected int getNotificationColor() {
+ int notificationColor = 0;
+ String packageName = mService.getPackageName();
+ try {
+ Context packageContext = mService.createPackageContext(packageName, 0);
+ ApplicationInfo applicationInfo =
+ mService.getPackageManager().getApplicationInfo(packageName, 0);
+ packageContext.setTheme(applicationInfo.theme);
+ Resources.Theme theme = packageContext.getTheme();
+ TypedArray ta = theme.obtainStyledAttributes(
+ new int[] {android.R.attr.colorPrimary});
+ notificationColor = ta.getColor(0, Color.DKGRAY);
+ ta.recycle();
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return notificationColor;
+ }
+
+ /**
+ * Posts the notification and starts tracking the session to keep it
+ * updated. The notification will automatically be removed if the session is
+ * destroyed before {@link #stopNotification} is called.
+ */
+ public void startNotification() {
+ if (!mStarted) {
+ mController.registerCallback(mCb);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_NEXT);
+ filter.addAction(ACTION_PAUSE);
+ filter.addAction(ACTION_PLAY);
+ filter.addAction(ACTION_PREV);
+ mService.registerReceiver(this, filter);
+
+ mMetadata = mController.getMetadata();
+ mPlaybackState = mController.getPlaybackState();
+
+ mStarted = true;
+ // The notification must be updated after setting started to true
+ updateNotificationMetadata();
+ }
+ }
+
+ /**
+ * Removes the notification and stops tracking the session. If the session
+ * was destroyed this has no effect.
+ */
+ public void stopNotification() {
+ mStarted = false;
+ mController.unregisterCallback(mCb);
+ try {
+ mNotificationManager.cancel(NOTIFICATION_ID);
+ mService.unregisterReceiver(this);
+ } catch (IllegalArgumentException ex) {
+ // ignore if the receiver is not registered.
+ }
+ mService.stopForeground(true);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ LogHelper.d(TAG, "Received intent with action " + action);
+ if (ACTION_PAUSE.equals(action)) {
+ mTransportControls.pause();
+ } else if (ACTION_PLAY.equals(action)) {
+ mTransportControls.play();
+ } else if (ACTION_NEXT.equals(action)) {
+ mTransportControls.skipToNext();
+ } else if (ACTION_PREV.equals(action)) {
+ mTransportControls.skipToPrevious();
+ }
+ }
+
+ /**
+ * Update the state based on a change on the session token. Called either when
+ * we are running for the first time or when the media session owner has destroyed the session
+ * (see {@link android.media.session.MediaController.Callback#onSessionDestroyed()})
+ */
+ private void updateSessionToken() {
+ MediaSession.Token freshToken = mService.getSessionToken();
+ if (mSessionToken == null || !mSessionToken.equals(freshToken)) {
+ if (mController != null) {
+ mController.unregisterCallback(mCb);
+ }
+ mSessionToken = freshToken;
+ mController = new MediaController(mService, mSessionToken);
+ mTransportControls = mController.getTransportControls();
+ if (mStarted) {
+ mController.registerCallback(mCb);
+ }
+ }
+ }
+
+ private final MediaController.Callback mCb = new MediaController.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ mPlaybackState = state;
+ LogHelper.d(TAG, "Received new playback state", state);
+ updateNotificationPlaybackState();
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ mMetadata = metadata;
+ LogHelper.d(TAG, "Received new metadata ", metadata);
+ updateNotificationMetadata();
+ }
+
+ @Override
+ public void onSessionDestroyed() {
+ super.onSessionDestroyed();
+ LogHelper.d(TAG, "Session was destroyed, resetting to the new session token");
+ updateSessionToken();
+ }
+ };
+
+ private void updateNotificationMetadata() {
+ LogHelper.d(TAG, "updateNotificationMetadata. mMetadata=" + mMetadata);
+ if (mMetadata == null || mPlaybackState == null) {
+ return;
+ }
+
+ updatePlayPauseAction();
+
+ mNotificationBuilder = new Notification.Builder(mService);
+ int playPauseActionIndex = 0;
+
+ // If skip to previous action is enabled
+ if ((mPlaybackState.getActions() & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
+ mNotificationBuilder
+ .addAction(R.drawable.ic_skip_previous_white_24dp,
+ mService.getString(R.string.label_previous),
+ mIntents.get(R.drawable.ic_skip_previous_white_24dp));
+ playPauseActionIndex = 1;
+ }
+
+ mNotificationBuilder.addAction(mPlayPauseAction);
+
+ // If skip to next action is enabled
+ if ((mPlaybackState.getActions() & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+ mNotificationBuilder.addAction(R.drawable.ic_skip_next_white_24dp,
+ mService.getString(R.string.label_next),
+ mIntents.get(R.drawable.ic_skip_next_white_24dp));
+ }
+
+ MediaDescription description = mMetadata.getDescription();
+
+ String fetchArtUrl = null;
+ Bitmap art = description.getIconBitmap();
+ if (art == null && description.getIconUri() != null) {
+ // This sample assumes the iconUri will be a valid URL formatted String, but
+ // it can actually be any valid Android Uri formatted String.
+ // async fetch the album art icon
+ String artUrl = description.getIconUri().toString();
+ art = mAlbumArtCache.get(artUrl);
+ if (art == null) {
+ fetchArtUrl = artUrl;
+ // use a placeholder art while the remote art is being downloaded
+ art = BitmapFactory.decodeResource(mService.getResources(), R.drawable.ic_default_art);
+ }
+ }
+
+ mNotificationBuilder
+ .setStyle(new Notification.MediaStyle()
+ .setShowActionsInCompactView(playPauseActionIndex) // only show play/pause in compact view
+ .setMediaSession(mSessionToken))
+ .setColor(mNotificationColor)
+ .setSmallIcon(R.drawable.ic_notification)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setUsesChronometer(true)
+ .setContentTitle(description.getTitle())
+ .setContentText(description.getSubtitle())
+ .setLargeIcon(art);
+
+ updateNotificationPlaybackState();
+
+ mService.startForeground(NOTIFICATION_ID, mNotificationBuilder.build());
+ if (fetchArtUrl != null) {
+ fetchBitmapFromURLAsync(fetchArtUrl);
+ }
+ }
+
+ private void updatePlayPauseAction() {
+ LogHelper.d(TAG, "updatePlayPauseAction");
+ String playPauseLabel = "";
+ int playPauseIcon;
+ if (mPlaybackState.getState() == PlaybackState.STATE_PLAYING) {
+ playPauseLabel = mService.getString(R.string.label_pause);
+ playPauseIcon = R.drawable.ic_pause_white_24dp;
+ } else {
+ playPauseLabel = mService.getString(R.string.label_play);
+ playPauseIcon = R.drawable.ic_play_arrow_white_24dp;
+ }
+ if (mPlayPauseAction == null) {
+ mPlayPauseAction = new Notification.Action(playPauseIcon, playPauseLabel,
+ mIntents.get(playPauseIcon));
+ } else {
+ mPlayPauseAction.icon = playPauseIcon;
+ mPlayPauseAction.title = playPauseLabel;
+ mPlayPauseAction.actionIntent = mIntents.get(playPauseIcon);
+ }
+ }
+
+ private void updateNotificationPlaybackState() {
+ LogHelper.d(TAG, "updateNotificationPlaybackState. mPlaybackState=" + mPlaybackState);
+ if (mPlaybackState == null || !mStarted) {
+ LogHelper.d(TAG, "updateNotificationPlaybackState. cancelling notification!");
+ mService.stopForeground(true);
+ return;
+ }
+ if (mNotificationBuilder == null) {
+ LogHelper.d(TAG, "updateNotificationPlaybackState. there is no notificationBuilder. Ignoring request to update state!");
+ return;
+ }
+ if (mPlaybackState.getPosition() >= 0) {
+ LogHelper.d(TAG, "updateNotificationPlaybackState. updating playback position to ",
+ (System.currentTimeMillis() - mPlaybackState.getPosition()) / 1000, " seconds");
+ mNotificationBuilder
+ .setWhen(System.currentTimeMillis() - mPlaybackState.getPosition())
+ .setShowWhen(true)
+ .setUsesChronometer(true);
+ mNotificationBuilder.setShowWhen(true);
+ } else {
+ LogHelper.d(TAG, "updateNotificationPlaybackState. hiding playback position");
+ mNotificationBuilder
+ .setWhen(0)
+ .setShowWhen(false)
+ .setUsesChronometer(false);
+ }
+
+ updatePlayPauseAction();
+
+ // Make sure that the notification can be dismissed by the user when we are not playing:
+ mNotificationBuilder.setOngoing(mPlaybackState.getState() == PlaybackState.STATE_PLAYING);
+
+ mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
+ }
+
+ public void fetchBitmapFromURLAsync(final String source) {
+ LogHelper.d(TAG, "getBitmapFromURLAsync: starting asynctask to fetch ", source);
+ new AsyncTask<Void, Void, Bitmap>() {
+ @Override
+ protected Bitmap doInBackground(Void[] objects) {
+ Bitmap bitmap = null;
+ try {
+ bitmap = BitmapHelper.fetchAndRescaleBitmap(source,
+ BitmapHelper.MEDIA_ART_BIG_WIDTH, BitmapHelper.MEDIA_ART_BIG_HEIGHT);
+ mAlbumArtCache.put(source, bitmap);
+ } catch (IOException e) {
+ LogHelper.e(TAG, e, "getBitmapFromURLAsync: " + source);
+ }
+ return bitmap;
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null && mMetadata != null &&
+ mNotificationBuilder != null && mMetadata.getDescription() != null &&
+ !source.equals(mMetadata.getDescription().getIconUri())) {
+ // If the media is still the same, update the notification:
+ LogHelper.d(TAG, "getBitmapFromURLAsync: set bitmap to ", source);
+ mNotificationBuilder.setLargeIcon(bitmap);
+ mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
+ }
+ }
+ }.execute();
+ }
+
+}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MusicPlayerActivity.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MusicPlayerActivity.java
new file mode 100644
index 0000000..648d268
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MusicPlayerActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.mediabrowserservice;
+
+import android.app.Activity;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaController;
+import android.os.Bundle;
+
+/**
+ * Main activity for the music player.
+ */
+public class MusicPlayerActivity extends Activity
+ implements BrowseFragment.FragmentDataHelper {
+
+ private static final String TAG = MusicPlayerActivity.class.getSimpleName();
+
+ private MediaBrowser mMediaBrowser;
+ private MediaController mMediaController;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_player);
+ if (savedInstanceState == null) {
+ getFragmentManager().beginTransaction()
+ .add(R.id.container, BrowseFragment.newInstance(null))
+ .commit();
+ }
+ }
+
+ @Override
+ public void onMediaItemSelected(MediaBrowser.MediaItem item) {
+ if (item.isPlayable()) {
+ getMediaController().getTransportControls().playFromMediaId(item.getMediaId(), null);
+ QueueFragment queueFragment = QueueFragment.newInstance();
+ getFragmentManager().beginTransaction()
+ .replace(R.id.container, queueFragment)
+ .addToBackStack(null)
+ .commit();
+ } else if (item.isBrowsable()) {
+ getFragmentManager().beginTransaction()
+ .replace(R.id.container, BrowseFragment.newInstance(item.getMediaId()))
+ .addToBackStack(null)
+ .commit();
+ }
+ }
+}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MusicService.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MusicService.java
new file mode 100644
index 0000000..a7a9ae2
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MusicService.java
@@ -0,0 +1,936 @@
+/*
+ * Copyright (C) 2014 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.mediabrowserservice;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.WifiLock;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.service.media.MediaBrowserService;
+
+import com.example.android.mediabrowserservice.model.MusicProvider;
+import com.example.android.mediabrowserservice.utils.LogHelper;
+import com.example.android.mediabrowserservice.utils.MediaIDHelper;
+import com.example.android.mediabrowserservice.utils.QueueHelper;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.example.android.mediabrowserservice.utils.MediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE;
+import static com.example.android.mediabrowserservice.utils.MediaIDHelper.MEDIA_ID_ROOT;
+import static com.example.android.mediabrowserservice.utils.MediaIDHelper.createBrowseCategoryMediaID;
+import static com.example.android.mediabrowserservice.utils.MediaIDHelper.extractBrowseCategoryFromMediaID;
+
+/**
+ * This class provides a MediaBrowser through a service. It exposes the media library to a browsing
+ * client, through the onGetRoot and onLoadChildren methods. It also creates a MediaSession and
+ * exposes it through its MediaSession.Token, which allows the client to create a MediaController
+ * that connects to and send control commands to the MediaSession remotely. This is useful for
+ * user interfaces that need to interact with your media session, like Android Auto. You can
+ * (should) also use the same service from your app's UI, which gives a seamless playback
+ * experience to the user.
+ *
+ * To implement a MediaBrowserService, you need to:
+ *
+ * <ul>
+ *
+ * <li> Extend {@link android.service.media.MediaBrowserService}, implementing the media browsing
+ * related methods {@link android.service.media.MediaBrowserService#onGetRoot} and
+ * {@link android.service.media.MediaBrowserService#onLoadChildren};
+ * <li> In onCreate, start a new {@link android.media.session.MediaSession} and notify its parent
+ * with the session's token {@link android.service.media.MediaBrowserService#setSessionToken};
+ *
+ * <li> Set a callback on the
+ * {@link android.media.session.MediaSession#setCallback(android.media.session.MediaSession.Callback)}.
+ * The callback will receive all the user's actions, like play, pause, etc;
+ *
+ * <li> Handle all the actual music playing using any method your app prefers (for example,
+ * {@link android.media.MediaPlayer})
+ *
+ * <li> Update playbackState, "now playing" metadata and queue, using MediaSession proper methods
+ * {@link android.media.session.MediaSession#setPlaybackState(android.media.session.PlaybackState)}
+ * {@link android.media.session.MediaSession#setMetadata(android.media.MediaMetadata)} and
+ * {@link android.media.session.MediaSession#setQueue(java.util.List)})
+ *
+ * <li> Declare and export the service in AndroidManifest with an intent receiver for the action
+ * android.media.browse.MediaBrowserService
+ *
+ * </ul>
+ *
+ * To make your app compatible with Android Auto, you also need to:
+ *
+ * <ul>
+ *
+ * <li> Declare a meta-data tag in AndroidManifest.xml linking to a xml resource
+ * with a <automotiveApp> root element. For a media app, this must include
+ * an <uses name="media"/> element as a child.
+ * For example, in AndroidManifest.xml:
+ * <meta-data android:name="com.google.android.gms.car.application"
+ * android:resource="@xml/automotive_app_desc"/>
+ * And in res/values/automotive_app_desc.xml:
+ * <automotiveApp>
+ * <uses name="media"/>
+ * </automotiveApp>
+ *
+ * </ul>
+
+ * @see <a href="README.md">README.md</a> for more details.
+ *
+ */
+
+public class MusicService extends MediaBrowserService implements OnPreparedListener,
+ OnCompletionListener, OnErrorListener, AudioManager.OnAudioFocusChangeListener {
+
+ private static final String TAG = "MusicService";
+
+ // Action to thumbs up a media item
+ private static final String CUSTOM_ACTION_THUMBS_UP = "thumbs_up";
+ // Delay stopSelf by using a handler.
+ private static final int STOP_DELAY = 30000;
+
+ // The volume we set the media player to when we lose audio focus, but are
+ // allowed to reduce the volume instead of stopping playback.
+ public static final float VOLUME_DUCK = 0.2f;
+
+ // The volume we set the media player when we have audio focus.
+ public static final float VOLUME_NORMAL = 1.0f;
+ public static final String ANDROID_AUTO_PACKAGE_NAME = "com.google.android.projection.gearhead";
+ public static final String ANDROID_AUTO_EMULATOR_PACKAGE_NAME = "com.google.android.mediasimulator";
+
+ // Music catalog manager
+ private MusicProvider mMusicProvider;
+
+ private MediaSession mSession;
+ private MediaPlayer mMediaPlayer;
+
+ // "Now playing" queue:
+ private List<MediaSession.QueueItem> mPlayingQueue;
+ private int mCurrentIndexOnQueue;
+
+ // Current local media player state
+ private int mState = PlaybackState.STATE_NONE;
+
+ // Wifi lock that we hold when streaming files from the internet, in order
+ // to prevent the device from shutting off the Wifi radio
+ private WifiLock mWifiLock;
+
+ private MediaNotification mMediaNotification;
+
+ // Indicates whether the service was started.
+ private boolean mServiceStarted;
+
+ enum AudioFocus {
+ NoFocusNoDuck, // we don't have audio focus, and can't duck
+ NoFocusCanDuck, // we don't have focus, but can play at a low volume
+ // ("ducking")
+ Focused // we have full audio focus
+ }
+
+ // Type of audio focus we have:
+ private AudioFocus mAudioFocus = AudioFocus.NoFocusNoDuck;
+ private AudioManager mAudioManager;
+
+ // Indicates if we should start playing immediately after we gain focus.
+ private boolean mPlayOnFocusGain;
+
+ private Handler mDelayedStopHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if ((mMediaPlayer != null && mMediaPlayer.isPlaying()) ||
+ mPlayOnFocusGain) {
+ LogHelper.d(TAG, "Ignoring delayed stop since the media player is in use.");
+ return;
+ }
+ LogHelper.d(TAG, "Stopping service with delay handler.");
+ stopSelf();
+ mServiceStarted = false;
+ }
+ };
+
+ /*
+ * (non-Javadoc)
+ * @see android.app.Service#onCreate()
+ */
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ LogHelper.d(TAG, "onCreate");
+
+ mPlayingQueue = new ArrayList<>();
+
+ // Create the Wifi lock (this does not acquire the lock, this just creates it)
+ mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
+ .createWifiLock(WifiManager.WIFI_MODE_FULL, "MusicDemo_lock");
+
+
+ // Create the music catalog metadata provider
+ mMusicProvider = new MusicProvider();
+ mMusicProvider.retrieveMedia(new MusicProvider.Callback() {
+ @Override
+ public void onMusicCatalogReady(boolean success) {
+ mState = success ? PlaybackState.STATE_NONE : PlaybackState.STATE_ERROR;
+ }
+ });
+
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+
+ // Start a new MediaSession
+ mSession = new MediaSession(this, "MusicService");
+ setSessionToken(mSession.getSessionToken());
+ mSession.setCallback(new MediaSessionCallback());
+ mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
+ MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+
+ // Use these extras to reserve space for the corresponding actions, even when they are disabled
+ // in the playbackstate, so the custom actions don't reflow.
+ Bundle extras = new Bundle();
+ extras.putBoolean(
+ "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT",
+ true);
+ extras.putBoolean(
+ "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS",
+ true);
+ // If you want to reserve the Queue slot when there is no queue
+ // (mSession.setQueue(emptylist)), uncomment the lines below:
+ // extras.putBoolean(
+ // "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE",
+ // true);
+ mSession.setExtras(extras);
+
+ updatePlaybackState(null);
+
+ mMediaNotification = new MediaNotification(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see android.app.Service#onDestroy()
+ */
+ @Override
+ public void onDestroy() {
+ LogHelper.d(TAG, "onDestroy");
+
+ // Service is being killed, so make sure we release our resources
+ handleStopRequest(null);
+
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ // In particular, always release the MediaSession to clean up resources
+ // and notify associated MediaController(s).
+ mSession.release();
+ }
+
+
+ @Override
+ public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+ LogHelper.d(TAG, "OnGetRoot: clientPackageName=" + clientPackageName,
+ "; clientUid=" + clientUid + " ; rootHints=", rootHints);
+ // To ensure you are not allowing any arbitrary app to browse your app's contents, you
+ // need to check the origin:
+ if (!ANDROID_AUTO_PACKAGE_NAME.equals(clientPackageName) &&
+ !ANDROID_AUTO_EMULATOR_PACKAGE_NAME.equals(clientPackageName) &&
+ !getApplication().getPackageName().equals(clientPackageName)) {
+ // If the request comes from an untrusted package, return null. No further calls will
+ // be made to other media browsing methods.
+ LogHelper.w(TAG, "OnGetRoot: IGNORING request from untrusted package " + clientPackageName);
+ return null;
+ }
+ if (ANDROID_AUTO_PACKAGE_NAME.equals(clientPackageName)) {
+ // Optional: if your app needs to adapt ads, music library or anything else that
+ // needs to run differently when connected to the car, this is where you should handle
+ // it.
+ }
+ return new BrowserRoot(MEDIA_ID_ROOT, null);
+ }
+
+ @Override
+ public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
+ if (!mMusicProvider.isInitialized()) {
+ // Use result.detach to allow calling result.sendResult from another thread:
+ result.detach();
+
+ mMusicProvider.retrieveMedia(new MusicProvider.Callback() {
+ @Override
+ public void onMusicCatalogReady(boolean success) {
+ if (success) {
+ loadChildrenImpl(parentMediaId, result);
+ } else {
+ updatePlaybackState(getString(R.string.error_no_metadata));
+ result.sendResult(new ArrayList<MediaItem>());
+ }
+ }
+ });
+
+ } else {
+ // If our music catalog is already loaded/cached, load them into result immediately
+ loadChildrenImpl(parentMediaId, result);
+ }
+ }
+
+ /**
+ * Actual implementation of onLoadChildren that assumes that MusicProvider is already
+ * initialized.
+ */
+ private void loadChildrenImpl(final String parentMediaId,
+ final Result<List<MediaBrowser.MediaItem>> result) {
+ LogHelper.d(TAG, "OnLoadChildren: parentMediaId=", parentMediaId);
+
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ if (MEDIA_ID_ROOT.equals(parentMediaId)) {
+ LogHelper.d(TAG, "OnLoadChildren.ROOT");
+ mediaItems.add(new MediaBrowser.MediaItem(
+ new MediaDescription.Builder()
+ .setMediaId(MEDIA_ID_MUSICS_BY_GENRE)
+ .setTitle(getString(R.string.browse_genres))
+ .setIconUri(Uri.parse("android.resource://" +
+ "com.example.android.mediabrowserservice/drawable/ic_by_genre"))
+ .setSubtitle(getString(R.string.browse_genre_subtitle))
+ .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE
+ ));
+
+ } else if (MEDIA_ID_MUSICS_BY_GENRE.equals(parentMediaId)) {
+ LogHelper.d(TAG, "OnLoadChildren.GENRES");
+ for (String genre: mMusicProvider.getGenres()) {
+ MediaBrowser.MediaItem item = new MediaBrowser.MediaItem(
+ new MediaDescription.Builder()
+ .setMediaId(createBrowseCategoryMediaID(MEDIA_ID_MUSICS_BY_GENRE, genre))
+ .setTitle(genre)
+ .setSubtitle(getString(R.string.browse_musics_by_genre_subtitle, genre))
+ .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE
+ );
+ mediaItems.add(item);
+ }
+
+ } else if (parentMediaId.startsWith(MEDIA_ID_MUSICS_BY_GENRE)) {
+ String genre = extractBrowseCategoryFromMediaID(parentMediaId)[1];
+ LogHelper.d(TAG, "OnLoadChildren.SONGS_BY_GENRE genre=", genre);
+ for (MediaMetadata track: mMusicProvider.getMusicsByGenre(genre)) {
+ // Since mediaMetadata fields are immutable, we need to create a copy, so we
+ // can set a hierarchy-aware mediaID. We will need to know the media hierarchy
+ // when we get a onPlayFromMusicID call, so we can create the proper queue based
+ // on where the music was selected from (by artist, by genre, random, etc)
+ String hierarchyAwareMediaID = MediaIDHelper.createTrackMediaID(
+ MEDIA_ID_MUSICS_BY_GENRE, genre, track);
+ MediaMetadata trackCopy = new MediaMetadata.Builder(track)
+ .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, hierarchyAwareMediaID)
+ .build();
+ MediaBrowser.MediaItem bItem = new MediaBrowser.MediaItem(
+ trackCopy.getDescription(), MediaItem.FLAG_PLAYABLE);
+ mediaItems.add(bItem);
+ }
+ } else {
+ LogHelper.w(TAG, "Skipping unmatched parentMediaId: ", parentMediaId);
+ }
+ result.sendResult(mediaItems);
+ }
+
+
+
+ private final class MediaSessionCallback extends MediaSession.Callback {
+ @Override
+ public void onPlay() {
+ LogHelper.d(TAG, "play");
+
+ if (mPlayingQueue == null || mPlayingQueue.isEmpty()) {
+ mPlayingQueue = QueueHelper.getRandomQueue(mMusicProvider);
+ mSession.setQueue(mPlayingQueue);
+ mSession.setQueueTitle(getString(R.string.random_queue_title));
+ // start playing from the beginning of the queue
+ mCurrentIndexOnQueue = 0;
+ }
+
+ if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
+ handlePlayRequest();
+ }
+ }
+
+ @Override
+ public void onSkipToQueueItem(long queueId) {
+ LogHelper.d(TAG, "OnSkipToQueueItem:" + queueId);
+ if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
+
+ // set the current index on queue from the music Id:
+ mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(mPlayingQueue, queueId);
+
+ // play the music
+ handlePlayRequest();
+ }
+ }
+
+ @Override
+ public void onPlayFromMediaId(String mediaId, Bundle extras) {
+ LogHelper.d(TAG, "playFromMediaId mediaId:", mediaId, " extras=", extras);
+
+ // The mediaId used here is not the unique musicId. This one comes from the
+ // MediaBrowser, and is actually a "hierarchy-aware mediaID": a concatenation of
+ // the hierarchy in MediaBrowser and the actual unique musicID. This is necessary
+ // so we can build the correct playing queue, based on where the track was
+ // selected from.
+ mPlayingQueue = QueueHelper.getPlayingQueue(mediaId, mMusicProvider);
+ mSession.setQueue(mPlayingQueue);
+ String queueTitle = getString(R.string.browse_musics_by_genre_subtitle,
+ MediaIDHelper.extractBrowseCategoryValueFromMediaID(mediaId));
+ mSession.setQueueTitle(queueTitle);
+
+ if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
+ String uniqueMusicID = MediaIDHelper.extractMusicIDFromMediaID(mediaId);
+ // set the current index on queue from the music Id:
+ mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(
+ mPlayingQueue, uniqueMusicID);
+
+ // play the music
+ handlePlayRequest();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ LogHelper.d(TAG, "pause. current state=" + mState);
+ handlePauseRequest();
+ }
+
+ @Override
+ public void onStop() {
+ LogHelper.d(TAG, "stop. current state=" + mState);
+ handleStopRequest(null);
+ }
+
+ @Override
+ public void onSkipToNext() {
+ LogHelper.d(TAG, "skipToNext");
+ mCurrentIndexOnQueue++;
+ if (mPlayingQueue != null && mCurrentIndexOnQueue >= mPlayingQueue.size()) {
+ mCurrentIndexOnQueue = 0;
+ }
+ if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
+ mState = PlaybackState.STATE_PLAYING;
+ handlePlayRequest();
+ } else {
+ LogHelper.e(TAG, "skipToNext: cannot skip to next. next Index=" +
+ mCurrentIndexOnQueue + " queue length=" +
+ (mPlayingQueue == null ? "null" : mPlayingQueue.size()));
+ handleStopRequest("Cannot skip");
+ }
+ }
+
+ @Override
+ public void onSkipToPrevious() {
+ LogHelper.d(TAG, "skipToPrevious");
+
+ mCurrentIndexOnQueue--;
+ if (mPlayingQueue != null && mCurrentIndexOnQueue < 0) {
+ // This sample's behavior: skipping to previous when in first song restarts the
+ // first song.
+ mCurrentIndexOnQueue = 0;
+ }
+ if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
+ mState = PlaybackState.STATE_PLAYING;
+ handlePlayRequest();
+ } else {
+ LogHelper.e(TAG, "skipToPrevious: cannot skip to previous. previous Index=" +
+ mCurrentIndexOnQueue + " queue length=" +
+ (mPlayingQueue == null ? "null" : mPlayingQueue.size()));
+ handleStopRequest("Cannot skip");
+ }
+ }
+
+ @Override
+ public void onCustomAction(String action, Bundle extras) {
+ if (CUSTOM_ACTION_THUMBS_UP.equals(action)) {
+ LogHelper.i(TAG, "onCustomAction: favorite for current track");
+ MediaMetadata track = getCurrentPlayingMusic();
+ if (track != null) {
+ String mediaId = track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
+ mMusicProvider.setFavorite(mediaId, !mMusicProvider.isFavorite(mediaId));
+ }
+ updatePlaybackState(null);
+ } else {
+ LogHelper.e(TAG, "Unsupported action: ", action);
+ }
+
+ }
+
+ @Override
+ public void onPlayFromSearch(String query, Bundle extras) {
+ LogHelper.d(TAG, "playFromSearch query=", query);
+
+ mPlayingQueue = QueueHelper.getPlayingQueueFromSearch(query, mMusicProvider);
+ LogHelper.d(TAG, "playFromSearch playqueue.length=" + mPlayingQueue.size());
+ mSession.setQueue(mPlayingQueue);
+
+ if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
+
+ // start playing from the beginning of the queue
+ mCurrentIndexOnQueue = 0;
+
+ handlePlayRequest();
+ }
+ }
+ }
+
+
+
+ /*
+ * Called when media player is done playing current song.
+ * @see android.media.MediaPlayer.OnCompletionListener
+ */
+ @Override
+ public void onCompletion(MediaPlayer player) {
+ LogHelper.d(TAG, "onCompletion from MediaPlayer");
+ // The media player finished playing the current song, so we go ahead
+ // and start the next.
+ if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
+ // In this sample, we restart the playing queue when it gets to the end:
+ mCurrentIndexOnQueue++;
+ if (mCurrentIndexOnQueue >= mPlayingQueue.size()) {
+ mCurrentIndexOnQueue = 0;
+ }
+ handlePlayRequest();
+ } else {
+ // If there is nothing to play, we stop and release the resources:
+ handleStopRequest(null);
+ }
+ }
+
+ /*
+ * Called when media player is done preparing.
+ * @see android.media.MediaPlayer.OnPreparedListener
+ */
+ @Override
+ public void onPrepared(MediaPlayer player) {
+ LogHelper.d(TAG, "onPrepared from MediaPlayer");
+ // The media player is done preparing. That means we can start playing if we
+ // have audio focus.
+ configMediaPlayerState();
+ }
+
+ /**
+ * Called when there's an error playing media. When this happens, the media
+ * player goes to the Error state. We warn the user about the error and
+ * reset the media player.
+ *
+ * @see android.media.MediaPlayer.OnErrorListener
+ */
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ LogHelper.e(TAG, "Media player error: what=" + what + ", extra=" + extra);
+ handleStopRequest("MediaPlayer error " + what + " (" + extra + ")");
+ return true; // true indicates we handled the error
+ }
+
+
+
+
+ /**
+ * Called by AudioManager on audio focus changes.
+ */
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ LogHelper.d(TAG, "onAudioFocusChange. focusChange=" + focusChange);
+ if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ // We have gained focus:
+ mAudioFocus = AudioFocus.Focused;
+
+ } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS ||
+ focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT ||
+ focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+ // We have lost focus. If we can duck (low playback volume), we can keep playing.
+ // Otherwise, we need to pause the playback.
+ boolean canDuck = focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
+ mAudioFocus = canDuck ? AudioFocus.NoFocusCanDuck : AudioFocus.NoFocusNoDuck;
+
+ // If we are playing, we need to reset media player by calling configMediaPlayerState
+ // with mAudioFocus properly set.
+ if (mState == PlaybackState.STATE_PLAYING && !canDuck) {
+ // If we don't have audio focus and can't duck, we save the information that
+ // we were playing, so that we can resume playback once we get the focus back.
+ mPlayOnFocusGain = true;
+ }
+ } else {
+ LogHelper.e(TAG, "onAudioFocusChange: Ignoring unsupported focusChange: " + focusChange);
+ }
+
+ configMediaPlayerState();
+ }
+
+
+
+ /**
+ * Handle a request to play music
+ */
+ private void handlePlayRequest() {
+ LogHelper.d(TAG, "handlePlayRequest: mState=" + mState);
+
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ if (!mServiceStarted) {
+ LogHelper.v(TAG, "Starting service");
+ // The MusicService needs to keep running even after the calling MediaBrowser
+ // is disconnected. Call startService(Intent) and then stopSelf(..) when we no longer
+ // need to play media.
+ startService(new Intent(getApplicationContext(), MusicService.class));
+ mServiceStarted = true;
+ }
+
+ mPlayOnFocusGain = true;
+ tryToGetAudioFocus();
+
+ if (!mSession.isActive()) {
+ mSession.setActive(true);
+ }
+
+ // actually play the song
+ if (mState == PlaybackState.STATE_PAUSED) {
+ // If we're paused, just continue playback and restore the
+ // 'foreground service' state.
+ configMediaPlayerState();
+ } else {
+ // If we're stopped or playing a song,
+ // just go ahead to the new song and (re)start playing
+ playCurrentSong();
+ }
+ }
+
+
+ /**
+ * Handle a request to pause music
+ */
+ private void handlePauseRequest() {
+ LogHelper.d(TAG, "handlePauseRequest: mState=" + mState);
+
+ if (mState == PlaybackState.STATE_PLAYING) {
+ // Pause media player and cancel the 'foreground service' state.
+ mState = PlaybackState.STATE_PAUSED;
+ if (mMediaPlayer.isPlaying()) {
+ mMediaPlayer.pause();
+ }
+ // while paused, retain the MediaPlayer but give up audio focus
+ relaxResources(false);
+ giveUpAudioFocus();
+ }
+ updatePlaybackState(null);
+ }
+
+ /**
+ * Handle a request to stop music
+ */
+ private void handleStopRequest(String withError) {
+ LogHelper.d(TAG, "handleStopRequest: mState=" + mState + " error=", withError);
+ mState = PlaybackState.STATE_STOPPED;
+
+ // let go of all resources...
+ relaxResources(true);
+ giveUpAudioFocus();
+ updatePlaybackState(withError);
+
+ mMediaNotification.stopNotification();
+
+ // service is no longer necessary. Will be started again if needed.
+ stopSelf();
+ mServiceStarted = false;
+ }
+
+ /**
+ * Releases resources used by the service for playback. This includes the
+ * "foreground service" status, the wake locks and possibly the MediaPlayer.
+ *
+ * @param releaseMediaPlayer Indicates whether the Media Player should also
+ * be released or not
+ */
+ private void relaxResources(boolean releaseMediaPlayer) {
+ LogHelper.d(TAG, "relaxResources. releaseMediaPlayer=" + releaseMediaPlayer);
+ // stop being a foreground service
+ stopForeground(true);
+
+ // reset the delayed stop handler.
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
+
+ // stop and release the Media Player, if it's available
+ if (releaseMediaPlayer && mMediaPlayer != null) {
+ mMediaPlayer.reset();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+
+ // we can also release the Wifi lock, if we're holding it
+ if (mWifiLock.isHeld()) {
+ mWifiLock.release();
+ }
+ }
+
+ /**
+ * Reconfigures MediaPlayer according to audio focus settings and
+ * starts/restarts it. This method starts/restarts the MediaPlayer
+ * respecting the current audio focus state. So if we have focus, it will
+ * play normally; if we don't have focus, it will either leave the
+ * MediaPlayer paused or set it to a low volume, depending on what is
+ * allowed by the current focus settings. This method assumes mPlayer !=
+ * null, so if you are calling it, you have to do so from a context where
+ * you are sure this is the case.
+ */
+ private void configMediaPlayerState() {
+ LogHelper.d(TAG, "configAndStartMediaPlayer. mAudioFocus=" + mAudioFocus);
+ if (mAudioFocus == AudioFocus.NoFocusNoDuck) {
+ // If we don't have audio focus and can't duck, we have to pause,
+ if (mState == PlaybackState.STATE_PLAYING) {
+ handlePauseRequest();
+ }
+ } else { // we have audio focus:
+ if (mAudioFocus == AudioFocus.NoFocusCanDuck) {
+ mMediaPlayer.setVolume(VOLUME_DUCK, VOLUME_DUCK); // we'll be relatively quiet
+ } else {
+ mMediaPlayer.setVolume(VOLUME_NORMAL, VOLUME_NORMAL); // we can be loud again
+ }
+ // If we were playing when we lost focus, we need to resume playing.
+ if (mPlayOnFocusGain) {
+ if (!mMediaPlayer.isPlaying()) {
+ LogHelper.d(TAG, "configAndStartMediaPlayer startMediaPlayer.");
+ mMediaPlayer.start();
+ }
+ mPlayOnFocusGain = false;
+ mState = PlaybackState.STATE_PLAYING;
+ }
+ }
+ updatePlaybackState(null);
+ }
+
+ /**
+ * Makes sure the media player exists and has been reset. This will create
+ * the media player if needed, or reset the existing media player if one
+ * already exists.
+ */
+ private void createMediaPlayerIfNeeded() {
+ LogHelper.d(TAG, "createMediaPlayerIfNeeded. needed? " + (mMediaPlayer==null));
+ if (mMediaPlayer == null) {
+ mMediaPlayer = new MediaPlayer();
+
+ // Make sure the media player will acquire a wake-lock while
+ // playing. If we don't do that, the CPU might go to sleep while the
+ // song is playing, causing playback to stop.
+ mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
+
+ // we want the media player to notify us when it's ready preparing,
+ // and when it's done playing:
+ mMediaPlayer.setOnPreparedListener(this);
+ mMediaPlayer.setOnCompletionListener(this);
+ mMediaPlayer.setOnErrorListener(this);
+ } else {
+ mMediaPlayer.reset();
+ }
+ }
+
+ /**
+ * Starts playing the current song in the playing queue.
+ */
+ void playCurrentSong() {
+ MediaMetadata track = getCurrentPlayingMusic();
+ if (track == null) {
+ LogHelper.e(TAG, "playSong: ignoring request to play next song, because cannot" +
+ " find it." +
+ " currentIndex=" + mCurrentIndexOnQueue +
+ " playQueue.size=" + (mPlayingQueue==null?"null": mPlayingQueue.size()));
+ return;
+ }
+ String source = track.getString(MusicProvider.CUSTOM_METADATA_TRACK_SOURCE);
+ LogHelper.d(TAG, "playSong: current (" + mCurrentIndexOnQueue + ") in playingQueue. " +
+ " musicId=" + track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) +
+ " source=" + source);
+
+ mState = PlaybackState.STATE_STOPPED;
+ relaxResources(false); // release everything except MediaPlayer
+
+ try {
+ createMediaPlayerIfNeeded();
+
+ mState = PlaybackState.STATE_BUFFERING;
+
+ mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mMediaPlayer.setDataSource(source);
+
+ // Starts preparing the media player in the background. When
+ // it's done, it will call our OnPreparedListener (that is,
+ // the onPrepared() method on this class, since we set the
+ // listener to 'this'). Until the media player is prepared,
+ // we *cannot* call start() on it!
+ mMediaPlayer.prepareAsync();
+
+ // If we are streaming from the internet, we want to hold a
+ // Wifi lock, which prevents the Wifi radio from going to
+ // sleep while the song is playing.
+ mWifiLock.acquire();
+
+ updatePlaybackState(null);
+ updateMetadata();
+
+ } catch (IOException ex) {
+ LogHelper.e(TAG, ex, "IOException playing song");
+ updatePlaybackState(ex.getMessage());
+ }
+ }
+
+
+
+ private void updateMetadata() {
+ if (!QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
+ LogHelper.e(TAG, "Can't retrieve current metadata.");
+ mState = PlaybackState.STATE_ERROR;
+ updatePlaybackState(getResources().getString(R.string.error_no_metadata));
+ return;
+ }
+ MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentIndexOnQueue);
+ String mediaId = queueItem.getDescription().getMediaId();
+ MediaMetadata track = mMusicProvider.getMusic(mediaId);
+ String trackId = track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
+ if (!mediaId.equals(trackId)) {
+ throw new IllegalStateException("track ID (" + trackId + ") " +
+ "should match mediaId (" + mediaId + ")");
+ }
+ LogHelper.d(TAG, "Updating metadata for MusicID= " + mediaId);
+ mSession.setMetadata(track);
+ }
+
+
+ /**
+ * Update the current media player state, optionally showing an error message.
+ *
+ * @param error if not null, error message to present to the user.
+ *
+ */
+ private void updatePlaybackState(String error) {
+
+ LogHelper.d(TAG, "updatePlaybackState, setting session playback state to " + mState);
+ long position = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+ if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
+ position = mMediaPlayer.getCurrentPosition();
+ }
+ PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
+ .setActions(getAvailableActions());
+
+ setCustomAction(stateBuilder);
+
+ // If there is an error message, send it to the playback state:
+ if (error != null) {
+ // Error states are really only supposed to be used for errors that cause playback to
+ // stop unexpectedly and persist until the user takes action to fix it.
+ stateBuilder.setErrorMessage(error);
+ mState = PlaybackState.STATE_ERROR;
+ }
+ stateBuilder.setState(mState, position, 1.0f, SystemClock.elapsedRealtime());
+
+ // Set the activeQueueItemId if the current index is valid.
+ if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
+ MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue);
+ stateBuilder.setActiveQueueItemId(item.getQueueId());
+ }
+
+ mSession.setPlaybackState(stateBuilder.build());
+
+ if (mState == PlaybackState.STATE_PLAYING || mState == PlaybackState.STATE_PAUSED) {
+ mMediaNotification.startNotification();
+ }
+ }
+
+ private void setCustomAction(PlaybackState.Builder stateBuilder) {
+ MediaMetadata currentMusic = getCurrentPlayingMusic();
+ if (currentMusic != null) {
+ // Set appropriate "Favorite" icon on Custom action:
+ String mediaId = currentMusic.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
+ int favoriteIcon = R.drawable.ic_star_off;
+ if (mMusicProvider.isFavorite(mediaId)) {
+ favoriteIcon = R.drawable.ic_star_on;
+ }
+ LogHelper.d(TAG, "updatePlaybackState, setting Favorite custom action of music ",
+ mediaId, " current favorite=", mMusicProvider.isFavorite(mediaId));
+ stateBuilder.addCustomAction(CUSTOM_ACTION_THUMBS_UP, getString(R.string.favorite),
+ favoriteIcon);
+ }
+ }
+
+ private long getAvailableActions() {
+ long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID |
+ PlaybackState.ACTION_PLAY_FROM_SEARCH;
+ if (mPlayingQueue == null || mPlayingQueue.isEmpty()) {
+ return actions;
+ }
+ if (mState == PlaybackState.STATE_PLAYING) {
+ actions |= PlaybackState.ACTION_PAUSE;
+ }
+ if (mCurrentIndexOnQueue > 0) {
+ actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+ }
+ if (mCurrentIndexOnQueue < mPlayingQueue.size() - 1) {
+ actions |= PlaybackState.ACTION_SKIP_TO_NEXT;
+ }
+ return actions;
+ }
+
+ private MediaMetadata getCurrentPlayingMusic() {
+ if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
+ MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue);
+ if (item != null) {
+ LogHelper.d(TAG, "getCurrentPlayingMusic for musicId=",
+ item.getDescription().getMediaId());
+ return mMusicProvider.getMusic(item.getDescription().getMediaId());
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Try to get the system audio focus.
+ */
+ void tryToGetAudioFocus() {
+ LogHelper.d(TAG, "tryToGetAudioFocus");
+ if (mAudioFocus != AudioFocus.Focused) {
+ int result = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+ if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ mAudioFocus = AudioFocus.Focused;
+ }
+ }
+ }
+
+ /**
+ * Give up the audio focus.
+ */
+ void giveUpAudioFocus() {
+ LogHelper.d(TAG, "giveUpAudioFocus");
+ if (mAudioFocus == AudioFocus.Focused) {
+ if (mAudioManager.abandonAudioFocus(this) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ mAudioFocus = AudioFocus.NoFocusNoDuck;
+ }
+ }
+ }
+}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/QueueAdapter.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/QueueAdapter.java
new file mode 100644
index 0000000..4f24e99
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/QueueAdapter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.mediabrowserservice;
+
+import android.app.Activity;
+import android.media.session.MediaSession;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * A list adapter for items in a queue
+ */
+public class QueueAdapter extends ArrayAdapter<MediaSession.QueueItem> {
+
+ // The currently selected/active queue item Id.
+ private long mActiveQueueItemId = MediaSession.QueueItem.UNKNOWN_ID;
+
+ public QueueAdapter(Activity context) {
+ super(context, R.layout.media_list_item, new ArrayList<MediaSession.QueueItem>());
+ }
+
+ public void setActiveQueueItemId(long id) {
+ this.mActiveQueueItemId = id;
+ }
+
+ private static class ViewHolder {
+ ImageView mImageView;
+ TextView mTitleView;
+ TextView mDescriptionView;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewHolder holder;
+
+ if (convertView == null) {
+ convertView = LayoutInflater.from(getContext())
+ .inflate(R.layout.media_list_item, parent, false);
+ holder = new ViewHolder();
+ holder.mImageView = (ImageView) convertView.findViewById(R.id.play_eq);
+ holder.mTitleView = (TextView) convertView.findViewById(R.id.title);
+ holder.mDescriptionView = (TextView) convertView.findViewById(R.id.description);
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
+
+ MediaSession.QueueItem item = getItem(position);
+ holder.mTitleView.setText(item.getDescription().getTitle());
+ if (item.getDescription().getDescription() != null) {
+ holder.mDescriptionView.setText(item.getDescription().getDescription());
+ }
+
+ // If the itemId matches the active Id then use a different icon
+ if (mActiveQueueItemId == item.getQueueId()) {
+ holder.mImageView.setImageDrawable(
+ getContext().getDrawable(R.drawable.ic_equalizer_white_24dp));
+ } else {
+ holder.mImageView.setImageDrawable(
+ getContext().getDrawable(R.drawable.ic_play_arrow_white_24dp));
+ }
+ return convertView;
+ }
+}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/QueueFragment.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/QueueFragment.java
new file mode 100644
index 0000000..f6076bc
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/QueueFragment.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.mediabrowserservice;
+
+import android.app.Fragment;
+import android.content.ComponentName;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ImageButton;
+import android.widget.ListView;
+
+import com.example.android.mediabrowserservice.utils.LogHelper;
+
+import java.util.List;
+
+/**
+ * A class that shows the Media Queue to the user.
+ */
+public class QueueFragment extends Fragment {
+
+ private static final String TAG = QueueFragment.class.getSimpleName();
+
+ private ImageButton mSkipNext;
+ private ImageButton mSkipPrevious;
+ private ImageButton mPlayPause;
+
+ private MediaBrowser mMediaBrowser;
+ private MediaController.TransportControls mTransportControls;
+ private MediaController mMediaController;
+ private PlaybackState mPlaybackState;
+
+ private QueueAdapter mQueueAdapter;
+
+ private MediaBrowser.ConnectionCallback mConnectionCallback =
+ new MediaBrowser.ConnectionCallback() {
+ @Override
+ public void onConnected() {
+ LogHelper.d(TAG, "onConnected: session token ", mMediaBrowser.getSessionToken());
+
+ if (mMediaBrowser.getSessionToken() == null) {
+ throw new IllegalArgumentException("No Session token");
+ }
+
+ mMediaController = new MediaController(getActivity(),
+ mMediaBrowser.getSessionToken());
+ mTransportControls = mMediaController.getTransportControls();
+ mMediaController.registerCallback(mSessionCallback);
+
+ getActivity().setMediaController(mMediaController);
+ mPlaybackState = mMediaController.getPlaybackState();
+
+ List<MediaSession.QueueItem> queue = mMediaController.getQueue();
+ if (queue != null) {
+ mQueueAdapter.clear();
+ mQueueAdapter.notifyDataSetInvalidated();
+ mQueueAdapter.addAll(queue);
+ mQueueAdapter.notifyDataSetChanged();
+ }
+ onPlaybackStateChanged(mPlaybackState);
+ }
+
+ @Override
+ public void onConnectionFailed() {
+ LogHelper.d(TAG, "onConnectionFailed");
+ }
+
+ @Override
+ public void onConnectionSuspended() {
+ LogHelper.d(TAG, "onConnectionSuspended");
+ mMediaController.unregisterCallback(mSessionCallback);
+ mTransportControls = null;
+ mMediaController = null;
+ getActivity().setMediaController(null);
+ }
+ };
+
+ // Receive callbacks from the MediaController. Here we update our state such as which queue
+ // is being shown, the current title and description and the PlaybackState.
+ private MediaController.Callback mSessionCallback = new MediaController.Callback() {
+
+ @Override
+ public void onSessionDestroyed() {
+ LogHelper.d(TAG, "Session destroyed. Need to fetch a new Media Session");
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ if (state == null) {
+ return;
+ }
+ LogHelper.d(TAG, "Received playback state change to state ", state.getState());
+ mPlaybackState = state;
+ QueueFragment.this.onPlaybackStateChanged(state);
+ }
+
+ @Override
+ public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+ LogHelper.d(TAG, "onQueueChanged ", queue);
+ if (queue != null) {
+ mQueueAdapter.clear();
+ mQueueAdapter.notifyDataSetInvalidated();
+ mQueueAdapter.addAll(queue);
+ mQueueAdapter.notifyDataSetChanged();
+ }
+ }
+ };
+
+ public static QueueFragment newInstance() {
+ return new QueueFragment();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_list, container, false);
+
+ mSkipPrevious = (ImageButton) rootView.findViewById(R.id.skip_previous);
+ mSkipPrevious.setEnabled(false);
+ mSkipPrevious.setOnClickListener(mButtonListener);
+
+ mSkipNext = (ImageButton) rootView.findViewById(R.id.skip_next);
+ mSkipNext.setEnabled(false);
+ mSkipNext.setOnClickListener(mButtonListener);
+
+ mPlayPause = (ImageButton) rootView.findViewById(R.id.play_pause);
+ mPlayPause.setEnabled(true);
+ mPlayPause.setOnClickListener(mButtonListener);
+
+ mQueueAdapter = new QueueAdapter(getActivity());
+
+ ListView mListView = (ListView) rootView.findViewById(R.id.list_view);
+ mListView.setAdapter(mQueueAdapter);
+ mListView.setFocusable(true);
+ mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ MediaSession.QueueItem item = mQueueAdapter.getItem(position);
+ mTransportControls.skipToQueueItem(item.getQueueId());
+ }
+ });
+
+ mMediaBrowser = new MediaBrowser(getActivity(),
+ new ComponentName(getActivity(), MusicService.class),
+ mConnectionCallback, null);
+
+ return rootView;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mMediaBrowser != null) {
+ mMediaBrowser.connect();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mSessionCallback);
+ }
+ if (mMediaBrowser != null) {
+ mMediaBrowser.disconnect();
+ }
+ }
+
+
+ private void onPlaybackStateChanged(PlaybackState state) {
+ LogHelper.d(TAG, "onPlaybackStateChanged ", state);
+ if (state == null) {
+ return;
+ }
+ mQueueAdapter.setActiveQueueItemId(state.getActiveQueueItemId());
+ mQueueAdapter.notifyDataSetChanged();
+ boolean enablePlay = false;
+ StringBuilder statusBuilder = new StringBuilder();
+ switch (state.getState()) {
+ case PlaybackState.STATE_PLAYING:
+ statusBuilder.append("playing");
+ enablePlay = false;
+ break;
+ case PlaybackState.STATE_PAUSED:
+ statusBuilder.append("paused");
+ enablePlay = true;
+ break;
+ case PlaybackState.STATE_STOPPED:
+ statusBuilder.append("ended");
+ enablePlay = true;
+ break;
+ case PlaybackState.STATE_ERROR:
+ statusBuilder.append("error: ").append(state.getErrorMessage());
+ break;
+ case PlaybackState.STATE_BUFFERING:
+ statusBuilder.append("buffering");
+ break;
+ case PlaybackState.STATE_NONE:
+ statusBuilder.append("none");
+ enablePlay = false;
+ break;
+ case PlaybackState.STATE_CONNECTING:
+ statusBuilder.append("connecting");
+ break;
+ default:
+ statusBuilder.append(mPlaybackState);
+ }
+ statusBuilder.append(" -- At position: ").append(state.getPosition());
+ LogHelper.d(TAG, statusBuilder.toString());
+
+ if (enablePlay) {
+ mPlayPause.setImageDrawable(
+ getActivity().getDrawable(R.drawable.ic_play_arrow_white_24dp));
+ } else {
+ mPlayPause.setImageDrawable(getActivity().getDrawable(R.drawable.ic_pause_white_24dp));
+ }
+
+ mSkipPrevious.setEnabled((state.getActions() & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0);
+ mSkipNext.setEnabled((state.getActions() & PlaybackState.ACTION_SKIP_TO_NEXT) != 0);
+
+ LogHelper.d(TAG, "Queue From MediaController *** Title " +
+ mMediaController.getQueueTitle() + "\n: Queue: " + mMediaController.getQueue() +
+ "\n Metadata " + mMediaController.getMetadata());
+ }
+
+ private View.OnClickListener mButtonListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final int state = mPlaybackState == null ?
+ PlaybackState.STATE_NONE : mPlaybackState.getState();
+ switch (v.getId()) {
+ case R.id.play_pause:
+ LogHelper.d(TAG, "Play button pressed, in state " + state);
+ if (state == PlaybackState.STATE_PAUSED ||
+ state == PlaybackState.STATE_STOPPED ||
+ state == PlaybackState.STATE_NONE) {
+ playMedia();
+ } else if (state == PlaybackState.STATE_PLAYING) {
+ pauseMedia();
+ }
+ break;
+ case R.id.skip_previous:
+ LogHelper.d(TAG, "Start button pressed, in state " + state);
+ skipToPrevious();
+ break;
+ case R.id.skip_next:
+ skipToNext();
+ break;
+ }
+ }
+ };
+
+ private void playMedia() {
+ if (mTransportControls != null) {
+ mTransportControls.play();
+ }
+ }
+
+ private void pauseMedia() {
+ if (mTransportControls != null) {
+ mTransportControls.pause();
+ }
+ }
+
+ private void skipToPrevious() {
+ if (mTransportControls != null) {
+ mTransportControls.skipToPrevious();
+ }
+ }
+
+ private void skipToNext() {
+ if (mTransportControls != null) {
+ mTransportControls.skipToNext();
+ }
+ }
+}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/model/MusicProvider.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/model/MusicProvider.java
new file mode 100644
index 0000000..ae90fb0
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/model/MusicProvider.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2014 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.mediabrowserservice.model;
+
+import android.media.MediaMetadata;
+import android.os.AsyncTask;
+
+import com.example.android.mediabrowserservice.utils.LogHelper;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Utility class to get a list of MusicTrack's based on a server-side JSON
+ * configuration.
+ */
+public class MusicProvider {
+
+ private static final String TAG = "MusicProvider";
+
+ private static final String CATALOG_URL = "http://storage.googleapis.com/automotive-media/music.json";
+
+ public static final String CUSTOM_METADATA_TRACK_SOURCE = "__SOURCE__";
+
+ private static String JSON_MUSIC = "music";
+ private static String JSON_TITLE = "title";
+ private static String JSON_ALBUM = "album";
+ private static String JSON_ARTIST = "artist";
+ private static String JSON_GENRE = "genre";
+ private static String JSON_SOURCE = "source";
+ private static String JSON_IMAGE = "image";
+ private static String JSON_TRACK_NUMBER = "trackNumber";
+ private static String JSON_TOTAL_TRACK_COUNT = "totalTrackCount";
+ private static String JSON_DURATION = "duration";
+
+ private final ReentrantLock initializationLock = new ReentrantLock();
+
+ // Categorized caches for music track data:
+ private final HashMap<String, List<MediaMetadata>> mMusicListByGenre;
+ private final HashMap<String, MediaMetadata> mMusicListById;
+
+ private final HashSet<String> mFavoriteTracks;
+
+ enum State {
+ NON_INITIALIZED, INITIALIZING, INITIALIZED;
+ }
+
+ private State mCurrentState = State.NON_INITIALIZED;
+
+
+ public interface Callback {
+ void onMusicCatalogReady(boolean success);
+ }
+
+ public MusicProvider() {
+ mMusicListByGenre = new HashMap<>();
+ mMusicListById = new HashMap<>();
+ mFavoriteTracks = new HashSet<>();
+ }
+
+ /**
+ * Get an iterator over the list of genres
+ *
+ * @return
+ */
+ public Iterable<String> getGenres() {
+ if (mCurrentState != State.INITIALIZED) {
+ return new ArrayList<String>(0);
+ }
+ return mMusicListByGenre.keySet();
+ }
+
+ /**
+ * Get music tracks of the given genre
+ *
+ * @return
+ */
+ public Iterable<MediaMetadata> getMusicsByGenre(String genre) {
+ if (mCurrentState != State.INITIALIZED || !mMusicListByGenre.containsKey(genre)) {
+ return new ArrayList<MediaMetadata>();
+ }
+ return mMusicListByGenre.get(genre);
+ }
+
+ /**
+ * Very basic implementation of a search that filter music tracks which title containing
+ * the given query.
+ *
+ * @return
+ */
+ public Iterable<MediaMetadata> searchMusics(String titleQuery) {
+ ArrayList<MediaMetadata> result = new ArrayList<>();
+ if (mCurrentState != State.INITIALIZED) {
+ return result;
+ }
+ titleQuery = titleQuery.toLowerCase();
+ for (MediaMetadata track: mMusicListById.values()) {
+ if (track.getString(MediaMetadata.METADATA_KEY_TITLE).toLowerCase()
+ .contains(titleQuery)) {
+ result.add(track);
+ }
+ }
+ return result;
+ }
+
+ public MediaMetadata getMusic(String mediaId) {
+ return mMusicListById.get(mediaId);
+ }
+
+ public void setFavorite(String mediaId, boolean favorite) {
+ if (favorite) {
+ mFavoriteTracks.add(mediaId);
+ } else {
+ mFavoriteTracks.remove(mediaId);
+ }
+ }
+
+ public boolean isFavorite(String musicId) {
+ return mFavoriteTracks.contains(musicId);
+ }
+
+ public boolean isInitialized() {
+ return mCurrentState == State.INITIALIZED;
+ }
+
+ /**
+ * Get the list of music tracks from a server and caches the track information
+ * for future reference, keying tracks by mediaId and grouping by genre.
+ *
+ * @return
+ */
+ public void retrieveMedia(final Callback callback) {
+
+ if (mCurrentState == State.INITIALIZED) {
+ // Nothing to do, execute callback immediately
+ callback.onMusicCatalogReady(true);
+ return;
+ }
+
+ // Asynchronously load the music catalog in a separate thread
+ new AsyncTask() {
+ @Override
+ protected Object doInBackground(Object[] objects) {
+ retrieveMediaAsync(callback);
+ return null;
+ }
+ }.execute();
+ }
+
+ private void retrieveMediaAsync(Callback callback) {
+ initializationLock.lock();
+
+ try {
+ if (mCurrentState == State.NON_INITIALIZED) {
+ mCurrentState = State.INITIALIZING;
+
+ int slashPos = CATALOG_URL.lastIndexOf('/');
+ String path = CATALOG_URL.substring(0, slashPos + 1);
+ JSONObject jsonObj = parseUrl(CATALOG_URL);
+
+ JSONArray tracks = jsonObj.getJSONArray(JSON_MUSIC);
+ if (tracks != null) {
+ for (int j = 0; j < tracks.length(); j++) {
+ MediaMetadata item = buildFromJSON(tracks.getJSONObject(j), path);
+ String genre = item.getString(MediaMetadata.METADATA_KEY_GENRE);
+ List<MediaMetadata> list = mMusicListByGenre.get(genre);
+ if (list == null) {
+ list = new ArrayList<>();
+ }
+ list.add(item);
+ mMusicListByGenre.put(genre, list);
+ mMusicListById.put(item.getString(MediaMetadata.METADATA_KEY_MEDIA_ID),
+ item);
+ }
+ }
+ mCurrentState = State.INITIALIZED;
+ }
+ } catch (RuntimeException | JSONException e) {
+ LogHelper.e(TAG, e, "Could not retrieve music list");
+ } finally {
+ if (mCurrentState != State.INITIALIZED) {
+ // Something bad happened, so we reset state to NON_INITIALIZED to allow
+ // retries (eg if the network connection is temporary unavailable)
+ mCurrentState = State.NON_INITIALIZED;
+ }
+ initializationLock.unlock();
+ if (callback != null) {
+ callback.onMusicCatalogReady(mCurrentState == State.INITIALIZED);
+ }
+ }
+ }
+
+ private MediaMetadata buildFromJSON(JSONObject json, String basePath) throws JSONException {
+ String title = json.getString(JSON_TITLE);
+ String album = json.getString(JSON_ALBUM);
+ String artist = json.getString(JSON_ARTIST);
+ String genre = json.getString(JSON_GENRE);
+ String source = json.getString(JSON_SOURCE);
+ String iconUrl = json.getString(JSON_IMAGE);
+ int trackNumber = json.getInt(JSON_TRACK_NUMBER);
+ int totalTrackCount = json.getInt(JSON_TOTAL_TRACK_COUNT);
+ int duration = json.getInt(JSON_DURATION) * 1000; // ms
+
+ LogHelper.d(TAG, "Found music track: ", json);
+
+ // Media is stored relative to JSON file
+ if (!source.startsWith("http")) {
+ source = basePath + source;
+ }
+ if (!iconUrl.startsWith("http")) {
+ iconUrl = basePath + iconUrl;
+ }
+ // Since we don't have a unique ID in the server, we fake one using the hashcode of
+ // the music source. In a real world app, this could come from the server.
+ String id = String.valueOf(source.hashCode());
+
+ // Adding the music source to the MediaMetadata (and consequently using it in the
+ // mediaSession.setMetadata) is not a good idea for a real world music app, because
+ // the session metadata can be accessed by notification listeners. This is done in this
+ // sample for convenience only.
+ return new MediaMetadata.Builder()
+ .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, id)
+ .putString(CUSTOM_METADATA_TRACK_SOURCE, source)
+ .putString(MediaMetadata.METADATA_KEY_ALBUM, album)
+ .putString(MediaMetadata.METADATA_KEY_ARTIST, artist)
+ .putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+ .putString(MediaMetadata.METADATA_KEY_GENRE, genre)
+ .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, iconUrl)
+ .putString(MediaMetadata.METADATA_KEY_TITLE, title)
+ .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, trackNumber)
+ .putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, totalTrackCount)
+ .build();
+ }
+
+ /**
+ * Download a JSON file from a server, parse the content and return the JSON
+ * object.
+ *
+ * @param urlString
+ * @return
+ */
+ private JSONObject parseUrl(String urlString) {
+ InputStream is = null;
+ try {
+ java.net.URL url = new java.net.URL(urlString);
+ URLConnection urlConnection = url.openConnection();
+ is = new BufferedInputStream(urlConnection.getInputStream());
+ BufferedReader reader = new BufferedReader(new InputStreamReader(
+ urlConnection.getInputStream(), "iso-8859-1"));
+ StringBuilder sb = new StringBuilder();
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ }
+ return new JSONObject(sb.toString());
+ } catch (Exception e) {
+ LogHelper.e(TAG, "Failed to parse the json for media list", e);
+ return null;
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/utils/BitmapHelper.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/utils/BitmapHelper.java
new file mode 100644
index 0000000..5f0e767
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/utils/BitmapHelper.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.mediabrowserservice.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class BitmapHelper {
+
+ // Bitmap size for album art in media notifications when there are more than 3 playback actions
+ public static final int MEDIA_ART_SMALL_WIDTH=64;
+ public static final int MEDIA_ART_SMALL_HEIGHT=64;
+
+ // Bitmap size for album art in media notifications when there are no more than 3 playback actions
+ public static final int MEDIA_ART_BIG_WIDTH=128;
+ public static final int MEDIA_ART_BIG_HEIGHT=128;
+
+ public static final Bitmap scaleBitmap(int scaleFactor, InputStream is) {
+ // Get the dimensions of the bitmap
+ BitmapFactory.Options bmOptions = new BitmapFactory.Options();
+
+ // Decode the image file into a Bitmap sized to fill the View
+ bmOptions.inJustDecodeBounds = false;
+ bmOptions.inSampleSize = scaleFactor;
+
+ Bitmap bitmap = BitmapFactory.decodeStream(is, null, bmOptions);
+ return bitmap;
+ }
+
+ public static final int findScaleFactor(int targetW, int targetH, InputStream is) {
+ // Get the dimensions of the bitmap
+ BitmapFactory.Options bmOptions = new BitmapFactory.Options();
+ bmOptions.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(is, null, bmOptions);
+ int actualW = bmOptions.outWidth;
+ int actualH = bmOptions.outHeight;
+
+ // Determine how much to scale down the image
+ return Math.min(actualW/targetW, actualH/targetH);
+ }
+
+ public static final Bitmap fetchAndRescaleBitmap(String uri, int width, int height)
+ throws IOException {
+ URL url = new URL(uri);
+ HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
+ httpConnection.setDoInput(true);
+ httpConnection.connect();
+ InputStream inputStream = httpConnection.getInputStream();
+ int scaleFactor = findScaleFactor(width, height, inputStream);
+
+ httpConnection = (HttpURLConnection) url.openConnection();
+ httpConnection.setDoInput(true);
+ httpConnection.connect();
+ inputStream = httpConnection.getInputStream();
+ Bitmap bitmap = scaleBitmap(scaleFactor, inputStream);
+ return bitmap;
+ }
+
+}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/utils/LogHelper.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/utils/LogHelper.java
new file mode 100644
index 0000000..92b2e09
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/utils/LogHelper.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.mediabrowserservice.utils;
+
+import android.util.Log;
+
+public class LogHelper {
+ public static void v(String tag, Object... messages) {
+ log(tag, Log.VERBOSE, null, messages);
+ }
+
+ public static void d(String tag, Object... messages) {
+ log(tag, Log.DEBUG, null, messages);
+ }
+
+ public static void i(String tag, Object... messages) {
+ log(tag, Log.INFO, null, messages);
+ }
+
+ public static void w(String tag, Object... messages) {
+ log(tag, Log.WARN, null, messages);
+ }
+
+ public static void w(String tag, Throwable t, Object... messages) {
+ log(tag, Log.WARN, t, messages);
+ }
+
+ public static void e(String tag, Object... messages) {
+ log(tag, Log.ERROR, null, messages);
+ }
+
+ public static void e(String tag, Throwable t, Object... messages) {
+ log(tag, Log.ERROR, t, messages);
+ }
+
+ public static void log(String tag, int level, Throwable t, Object... messages) {
+ if (messages != null && Log.isLoggable(tag, level)) {
+ String message;
+ if (messages.length == 1) {
+ message = messages[0] == null ? null : messages[0].toString();
+ } else {
+ StringBuilder sb = new StringBuilder();
+ for (Object m: messages) {
+ sb.append(m);
+ }
+ if (t != null) {
+ sb.append("\n").append(Log.getStackTraceString(t));
+ }
+ message = sb.toString();
+ }
+ Log.println(level, tag, message);
+ }
+ }
+}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/utils/MediaIDHelper.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/utils/MediaIDHelper.java
new file mode 100644
index 0000000..68e6db9
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/utils/MediaIDHelper.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.mediabrowserservice.utils;
+
+import android.media.MediaMetadata;
+
+/**
+ * Utility class to help on queue related tasks.
+ */
+public class MediaIDHelper {
+
+ private static final String TAG = "MediaIDHelper";
+
+ // Media IDs used on browseable items of MediaBrowser
+ public static final String MEDIA_ID_ROOT = "__ROOT__";
+ public static final String MEDIA_ID_MUSICS_BY_GENRE = "__BY_GENRE__";
+
+ public static final String createTrackMediaID(String categoryType, String categoryValue,
+ MediaMetadata track) {
+ // MediaIDs are of the form <categoryType>/<categoryValue>|<musicUniqueId>, to make it easy to
+ // find the category (like genre) that a music was selected from, so we
+ // can correctly build the playing queue. This is specially useful when
+ // one music can appear in more than one list, like "by genre -> genre_1"
+ // and "by artist -> artist_1".
+ return categoryType + "/" + categoryValue + "|" +
+ track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
+ }
+
+ public static final String createBrowseCategoryMediaID(String categoryType, String categoryValue) {
+ return categoryType + "/" + categoryValue;
+ }
+
+ /**
+ * Extracts unique musicID from the mediaID. mediaID is, by this sample's convention, a
+ * concatenation of category (eg "by_genre"), categoryValue (eg "Classical") and unique
+ * musicID. This is necessary so we know where the user selected the music from, when the music
+ * exists in more than one music list, and thus we are able to correctly build the playing queue.
+ *
+ * @param musicID
+ * @return
+ */
+ public static final String extractMusicIDFromMediaID(String musicID) {
+ String[] segments = musicID.split("\\|", 2);
+ return segments.length == 2 ? segments[1] : null;
+ }
+
+ /**
+ * Extracts category and categoryValue from the mediaID. mediaID is, by this sample's
+ * convention, a concatenation of category (eg "by_genre"), categoryValue (eg "Classical") and
+ * mediaID. This is necessary so we know where the user selected the music from, when the music
+ * exists in more than one music list, and thus we are able to correctly build the playing queue.
+ *
+ * @param mediaID
+ * @return
+ */
+ public static final String[] extractBrowseCategoryFromMediaID(String mediaID) {
+ if (mediaID.indexOf('|') >= 0) {
+ mediaID = mediaID.split("\\|")[0];
+ }
+ if (mediaID.indexOf('/') == 0) {
+ return new String[]{mediaID, null};
+ } else {
+ return mediaID.split("/", 2);
+ }
+ }
+
+ public static final String extractBrowseCategoryValueFromMediaID(String mediaID) {
+ String[] categoryAndValue = extractBrowseCategoryFromMediaID(mediaID);
+ if (categoryAndValue != null && categoryAndValue.length == 2) {
+ return categoryAndValue[1];
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/utils/QueueHelper.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/utils/QueueHelper.java
new file mode 100644
index 0000000..abe3d34
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/utils/QueueHelper.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.mediabrowserservice.utils;
+
+import android.media.MediaMetadata;
+import android.media.session.MediaSession;
+
+import com.example.android.mediabrowserservice.model.MusicProvider;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import static com.example.android.mediabrowserservice.utils.MediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE;
+
+/**
+ * Utility class to help on queue related tasks.
+ */
+public class QueueHelper {
+
+ private static final String TAG = "QueueHelper";
+
+ public static final List<MediaSession.QueueItem> getPlayingQueue(String mediaId,
+ MusicProvider musicProvider) {
+
+ // extract the category and unique music ID from the media ID:
+ String[] category = MediaIDHelper.extractBrowseCategoryFromMediaID(mediaId);
+
+ // This sample only supports genre category.
+ if (!category[0].equals(MEDIA_ID_MUSICS_BY_GENRE) || category.length != 2) {
+ LogHelper.e(TAG, "Could not build a playing queue for this mediaId: ", mediaId);
+ return null;
+ }
+
+ String categoryValue = category[1];
+ LogHelper.e(TAG, "Creating playing queue for musics of genre ", categoryValue);
+
+ List<MediaSession.QueueItem> queue = convertToQueue(
+ musicProvider.getMusicsByGenre(categoryValue));
+
+ return queue;
+ }
+
+ public static final List<MediaSession.QueueItem> getPlayingQueueFromSearch(String query,
+ MusicProvider musicProvider) {
+
+ LogHelper.e(TAG, "Creating playing queue for musics from search ", query);
+
+ return convertToQueue(musicProvider.searchMusics(query));
+ }
+
+
+ public static final int getMusicIndexOnQueue(Iterable<MediaSession.QueueItem> queue,
+ String mediaId) {
+ int index = 0;
+ for (MediaSession.QueueItem item: queue) {
+ if (mediaId.equals(item.getDescription().getMediaId())) {
+ return index;
+ }
+ index++;
+ }
+ return -1;
+ }
+
+ public static final int getMusicIndexOnQueue(Iterable<MediaSession.QueueItem> queue,
+ long queueId) {
+ int index = 0;
+ for (MediaSession.QueueItem item: queue) {
+ if (queueId == item.getQueueId()) {
+ return index;
+ }
+ index++;
+ }
+ return -1;
+ }
+
+ private static final List<MediaSession.QueueItem> convertToQueue(
+ Iterable<MediaMetadata> tracks) {
+ List<MediaSession.QueueItem> queue = new ArrayList<>();
+ int count = 0;
+ for (MediaMetadata track : tracks) {
+ // We don't expect queues to change after created, so we use the item index as the
+ // queueId. Any other number unique in the queue would work.
+ MediaSession.QueueItem item = new MediaSession.QueueItem(
+ track.getDescription(), count++);
+ queue.add(item);
+ }
+ return queue;
+
+ }
+
+ /**
+ * Create a random queue. For simplicity sake, instead of a random queue, we create a
+ * queue using the first genre,
+ *
+ * @param musicProvider
+ * @return
+ */
+ public static final List<MediaSession.QueueItem> getRandomQueue(MusicProvider musicProvider) {
+ Iterator<String> genres = musicProvider.getGenres().iterator();
+ if (!genres.hasNext()) {
+ return new ArrayList<>();
+ }
+ String genre = genres.next();
+ Iterable<MediaMetadata> tracks = musicProvider.getMusicsByGenre(genre);
+
+ return convertToQueue(tracks);
+ }
+
+
+
+ public static final boolean isIndexPlayable(int index, List<MediaSession.QueueItem> queue) {
+ return (queue != null && index >= 0 && index < queue.size());
+ }
+}
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..47d6854
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_notification.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_notification.png
new file mode 100644
index 0000000..d8ea5a9
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_notification.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_pause_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
new file mode 100644
index 0000000..b4bdbb5
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 0000000..164385d
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_shuffle_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_shuffle_white_24dp.png
new file mode 100644
index 0000000..3eeb0ef
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_shuffle_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_next_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_next_white_24dp.png
new file mode 100644
index 0000000..4eaf7ca
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_next_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_previous_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_previous_white_24dp.png
new file mode 100644
index 0000000..e59dedb
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_previous_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..01b53fd
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-night-xxhdpi/ic_star_off.png b/media/MediaBrowserService/Application/src/main/res/drawable-night-xxhdpi/ic_star_off.png
new file mode 100644
index 0000000..e435d2a
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-night-xxhdpi/ic_star_off.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-night-xxhdpi/ic_star_on.png b/media/MediaBrowserService/Application/src/main/res/drawable-night-xxhdpi/ic_star_on.png
new file mode 100644
index 0000000..0c75bb6
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-night-xxhdpi/ic_star_on.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_equalizer_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_equalizer_white_24dp.png
new file mode 100644
index 0000000..dbba844
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_equalizer_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..af762f2
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
new file mode 100644
index 0000000..14b6d17
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 0000000..a55d199
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_shuffle_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_shuffle_white_24dp.png
new file mode 100644
index 0000000..8ce3a60
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_shuffle_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_next_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_next_white_24dp.png
new file mode 100644
index 0000000..f282b92
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_next_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_previous_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_previous_white_24dp.png
new file mode 100644
index 0000000..2522877
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_previous_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_by_genre.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_by_genre.png
new file mode 100644
index 0000000..da3b4a7
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_by_genre.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_default_art.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_default_art.png
new file mode 100644
index 0000000..dfb9e67
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_default_art.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_equalizer_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_equalizer_white_24dp.png
new file mode 100644
index 0000000..b82a8d9
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_equalizer_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..eef47aa
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
new file mode 100644
index 0000000..72dfa9f
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 0000000..043acd8
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_shuffle_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_shuffle_white_24dp.png
new file mode 100644
index 0000000..718b6b5
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_shuffle_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_next_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_next_white_24dp.png
new file mode 100644
index 0000000..4fe6088
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_next_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png
new file mode 100644
index 0000000..2c9310a
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_star_off.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_star_off.png
new file mode 100644
index 0000000..836085b
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_star_off.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_star_on.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_star_on.png
new file mode 100644
index 0000000..7cd6cfc
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_star_on.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/layout/activity_player.xml b/media/MediaBrowserService/Application/src/main/res/layout/activity_player.xml
new file mode 100644
index 0000000..21cdbbd
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/layout/activity_player.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".MusicPlayerActivity"
+ tools:ignore="MergeRootFrame" />
diff --git a/media/MediaBrowserService/Application/src/main/res/layout/fragment_list.xml b/media/MediaBrowserService/Application/src/main/res/layout/fragment_list.xml
new file mode 100644
index 0000000..c169fec
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/layout/fragment_list.xml
@@ -0,0 +1,60 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/fragment_list_padding">
+
+ <LinearLayout
+ android:id="@+id/controls"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <ImageButton
+ android:id="@+id/skip_previous"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:src="@drawable/ic_skip_previous_white_24dp"
+ android:contentDescription="@string/skip_previous"/>
+
+ <ImageButton
+ android:id="@+id/play_pause"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:src="@drawable/ic_play_arrow_white_24dp"
+ android:contentDescription="@string/play_pause"/>
+
+ <ImageButton
+ android:id="@+id/skip_next"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:src="@drawable/ic_skip_next_white_24dp"
+ android:contentDescription="@string/skip_next"/>
+
+ </LinearLayout>
+
+ <ListView
+ android:id="@+id/list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ </ListView>
+
+</LinearLayout>
diff --git a/media/MediaBrowserService/Application/src/main/res/layout/media_list_item.xml b/media/MediaBrowserService/Application/src/main/res/layout/media_list_item.xml
new file mode 100644
index 0000000..72c0ccf
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/layout/media_list_item.xml
@@ -0,0 +1,55 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:listPreferredItemHeight"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/play_eq"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:contentDescription="@string/play_item"
+ android:src="@drawable/ic_play_arrow_white_24dp"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:mode="twoLine"
+ android:padding="@dimen/list_item_padding"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/margin_text_view"
+ android:layout_marginTop="@dimen/margin_text_view"
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
+
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/margin_text_view"
+ android:layout_marginTop="@dimen/margin_text_view"
+ android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/media/MediaBrowserService/Application/src/main/res/values-v21/styles.xml b/media/MediaBrowserService/Application/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..21bb211
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/values-v21/styles.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+
+ <style name="AppBaseTheme" parent="android:Theme.Material">
+ <!-- colorPrimary is used for Notification icon and bottom facet bar icons
+ and overflow actions -->
+ <item name="android:colorPrimary">#ffff5722</item>
+
+ <!-- colorPrimaryDark is used for background -->
+ <item name="android:colorPrimaryDark">#ffbf360c</item>
+
+ <!-- colorAccent is sparingly used for accents, like floating action button highlight,
+ progress on playbar-->
+ <item name="android:colorAccent">#ffff5722</item>
+
+ </style>
+
+</resources>
diff --git a/media/MediaBrowserService/Application/src/main/res/values/dimens.xml b/media/MediaBrowserService/Application/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..e57a8c9
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+ <dimen name="fragment_list_padding">16dp</dimen>
+ <dimen name="list_item_padding">4dp</dimen>
+ <dimen name="margin_text_view">6dp</dimen>
+</resources>
diff --git a/media/MediaBrowserService/Application/src/main/res/values/strings.xml b/media/MediaBrowserService/Application/src/main/res/values/strings.xml
new file mode 100644
index 0000000..7a012e8
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/values/strings.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+
+ <string name="app_name">Auto Music Demo</string>
+ <string name="favorite">Favorite</string>
+ <string name="error_no_metadata">Unable to retrieve metadata.</string>
+ <string name="browse_genres">Genres</string>
+ <string name="browse_genre_subtitle">Songs by genre</string>
+ <string name="browse_musics_by_genre_subtitle">%1$s songs</string>
+ <string name="random_queue_title">Random music</string>
+ <string name="error_cannot_skip">Cannot skip</string>
+ <string name="error_loading_media">Error Loading Media</string>
+ <string name="play_item">Play item</string>
+ <string name="skip_previous">Skip to previous</string>
+ <string name="play_pause">play or pause</string>
+ <string name="skip_next">Skip to next</string>
+
+</resources>
diff --git a/media/MediaBrowserService/Application/src/main/res/values/strings_notifications.xml b/media/MediaBrowserService/Application/src/main/res/values/strings_notifications.xml
new file mode 100644
index 0000000..f406ba6
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/values/strings_notifications.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+
+ <string name="label_pause">Pause</string>
+ <string name="label_play">Play</string>
+ <string name="label_previous">Previous</string>
+ <string name="label_next">Next</string>
+ <string name="error_empty_metadata">Empty metadata!</string>
+</resources>
diff --git a/media/MediaBrowserService/Application/src/main/res/values/styles.xml b/media/MediaBrowserService/Application/src/main/res/values/styles.xml
new file mode 100644
index 0000000..3be59c1
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/values/styles.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+
+
+ <style name="AppTheme" parent="AppBaseTheme">
+ </style>
+
+ <style name="AppBaseTheme" parent="android:Theme.Light">
+ </style>
+
+</resources>
\ No newline at end of file
diff --git a/media/MediaBrowserService/Application/src/main/res/xml/automotive_app_desc.xml b/media/MediaBrowserService/Application/src/main/res/xml/automotive_app_desc.xml
new file mode 100644
index 0000000..a84750b
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/xml/automotive_app_desc.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<automotiveApp>
+ <uses name="media"/>
+</automotiveApp>
diff --git a/ui/notifications/BasicNotifications/CONTRIB.md b/media/MediaBrowserService/CONTRIB.md
similarity index 100%
copy from ui/notifications/BasicNotifications/CONTRIB.md
copy to media/MediaBrowserService/CONTRIB.md
diff --git a/ui/notifications/BasicNotifications/LICENSE b/media/MediaBrowserService/LICENSE
similarity index 100%
copy from ui/notifications/BasicNotifications/LICENSE
copy to media/MediaBrowserService/LICENSE
diff --git a/media/MediaBrowserService/README.md b/media/MediaBrowserService/README.md
new file mode 100644
index 0000000..b61692e
--- /dev/null
+++ b/media/MediaBrowserService/README.md
@@ -0,0 +1,123 @@
+Android MediaBrowserService Sample
+==============================
+
+This sample shows how to implement an audio media app that provides
+media library metadata and playback controls through a standard
+service. This sample is compatible with Android Auto.
+
+Introduction
+------------
+
+This sample shows how to implement an audio media app that provides
+media library metadata and playback controls through a standard
+service. It exposes a simple music library through the new
+MediaBrowserService and provides MediaSession callbacks. This allows
+it to be used in Android Auto, for example.
+When not connected to a car, the app has a very simple UI that browses
+the media library and provides simple playback controls. When
+connected to Android Auto, the same service provides data and callback
+to the Android Auto UI in the same manner as it provides them to the
+local UI.
+
+To implement a MediaBrowserService, you need to:
+
+- extend android.service.media.MediaBrowserService, implementing the media
+ browsing related methods onGetRoot and onLoadChildren;
+
+- in onCreate, start a new MediaSession and call super.setSessionToken() with
+ this MediaSession's token;
+
+- set a MediaSession.Callback class on the MediaSession. The callback class
+ will receive all the user's actions, like play, pause, etc;
+
+- handle all the actual music playing using any method your app prefers
+ (for example, the Android MediaPlayer class)
+
+- whenever it changes, update info about the playing item and the playing
+ queue using MediaSession corresponding methods (setMetadata,
+ setPlaybackState, setQueue, setQueueTitle, etc)
+
+- handle AudioManager focus change events and react appropriately
+ (e.g. pause when audio focus is lost)
+
+
+To make it compatible with Android Auto, you also need to:
+
+- declare a meta-data tag in AndroidManifest.xml linking to a xml resource
+ with a automotiveApp root element. For a media app, this must include
+ an <uses name="media"/> element as a child.
+
+ For example, in AndroidManifest.xml:
+```
+ <meta-data android:name="com.google.android.gms.car.application"
+ android:resource="@xml/automotive_app_desc"/>
+```
+
+ And in res/values/automotive\_app\_desc.xml:
+```
+ <?xml version="1.0" encoding="utf-8"?>
+ <automotiveApp>
+ <uses name="media"/>
+ </automotiveApp>
+```
+
+- declare and export the service in AndroidManifest:
+```
+ <service
+ android:name=".service.MusicService"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.media.browse.MediaBrowserService" />
+ </intent-filter>
+ </service>
+```
+
+
+Pre-requisites
+--------------
+
+- Android SDK v21
+
+Getting Started
+---------------
+
+This sample uses the Gradle build system. To build this project, use the
+"gradlew build" command or use "Import Project" in Android Studio.
+
+Screenshots
+-----------
+
+<!-- Update these to point to screenshots. Add more as needed. -->
+![Description](screenshots/image1.png) ![Description](screenshots/image2.png)
+
+Support
+-------
+
+- Google+ Community: https://plus.google.com/communities/105153134372062985968
+- Stack Overflow: http://stackoverflow.com/questions/tagged/android
+
+If you've found an error in this sample, please file an issue:
+https://github.com/googlesamples/android-MediaBrowserService
+
+Patches are encouraged, and may be submitted by forking this project and
+submitting a pull request through GitHub. Please see CONTRIBUTING.md for more details.
+
+License
+-------
+
+Copyright 2014 The Android Open Source Project, Inc.
+
+Licensed to the Apache Software Foundation (ASF) under one or more contributor
+license agreements. See the NOTICE file distributed with this work for
+additional information regarding copyright ownership. The ASF licenses this
+file to you under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
diff --git a/media/MediaBrowserService/build.gradle b/media/MediaBrowserService/build.gradle
new file mode 100644
index 0000000..2b8d1ef
--- /dev/null
+++ b/media/MediaBrowserService/build.gradle
@@ -0,0 +1,11 @@
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../build"
+ pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/ui/notifications/BasicNotifications/buildSrc/build.gradle b/media/MediaBrowserService/buildSrc/build.gradle
similarity index 63%
rename from ui/notifications/BasicNotifications/buildSrc/build.gradle
rename to media/MediaBrowserService/buildSrc/build.gradle
index e344a8c..8c294c2 100644
--- a/ui/notifications/BasicNotifications/buildSrc/build.gradle
+++ b/media/MediaBrowserService/buildSrc/build.gradle
@@ -1,6 +1,3 @@
-
-
-
repositories {
mavenCentral()
}
@@ -11,7 +8,7 @@
sourceSets {
main {
groovy {
- srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
+ srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
}
}
}
diff --git a/ui/notifications/LNotifications/gradle/wrapper/gradle-wrapper.jar b/media/MediaBrowserService/gradle/wrapper/gradle-wrapper.jar
similarity index 100%
copy from ui/notifications/LNotifications/gradle/wrapper/gradle-wrapper.jar
copy to media/MediaBrowserService/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/ui/notifications/BasicNotifications/gradle/wrapper/gradle-wrapper.properties b/media/MediaBrowserService/gradle/wrapper/gradle-wrapper.properties
similarity index 100%
copy from ui/notifications/BasicNotifications/gradle/wrapper/gradle-wrapper.properties
copy to media/MediaBrowserService/gradle/wrapper/gradle-wrapper.properties
diff --git a/ui/notifications/BasicNotifications/gradlew b/media/MediaBrowserService/gradlew
similarity index 100%
copy from ui/notifications/BasicNotifications/gradlew
copy to media/MediaBrowserService/gradlew
diff --git a/ui/notifications/BasicNotifications/gradlew.bat b/media/MediaBrowserService/gradlew.bat
similarity index 100%
copy from ui/notifications/BasicNotifications/gradlew.bat
copy to media/MediaBrowserService/gradlew.bat
diff --git a/media/MediaBrowserService/packaging.yaml b/media/MediaBrowserService/packaging.yaml
new file mode 100644
index 0000000..bcd9ac5
--- /dev/null
+++ b/media/MediaBrowserService/packaging.yaml
@@ -0,0 +1,15 @@
+# GOOGLE SAMPLE PACKAGING DATA
+#
+# This file is used by Google as part of our samples packaging process.
+# End users may safely ignore this file. It has no relevance to other systems.
+---
+
+status: PUBLISHED
+technologies: [Android]
+categories: [None]
+languages: [Java]
+solutions: [Mobile]
+github: googlesamples/android-MediaBrowserService
+level: BEGINNER
+icon: MediaBrowserServiceSample/src/main/res/drawable-xxhdpi/ic_launcher.png
+license: apache2-android
diff --git a/media/MediaBrowserService/settings.gradle b/media/MediaBrowserService/settings.gradle
new file mode 100644
index 0000000..7f11c5f
--- /dev/null
+++ b/media/MediaBrowserService/settings.gradle
@@ -0,0 +1,2 @@
+include 'Application'
+
diff --git a/ui/notifications/BasicNotifications/template-params.xml b/media/MediaBrowserService/template-params.xml
similarity index 62%
copy from ui/notifications/BasicNotifications/template-params.xml
copy to media/MediaBrowserService/template-params.xml
index 7118c24..c129f6a 100644
--- a/ui/notifications/BasicNotifications/template-params.xml
+++ b/media/MediaBrowserService/template-params.xml
@@ -14,23 +14,23 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
<sample>
- <name>BasicNotifications</name>
- <group>UI</group>
- <package>com.example.android.basicnotifications</package>
- <!-- change minSdk if needed-->
- <minSdk>8</minSdk>
+ <name>MediaBrowserService</name>
+ <group>Media</group>
+ <package>com.example.android.mediabrowserservice</package>
+
+ <minSdk>21</minSdk>
<strings>
<intro>
<![CDATA[
- This sample demonstrates how to display events in the system\'s notification bar. The
- NotificationCompat API is used for compatibility with older devices, running Android
- 2.2 (Froyo) or newer.
+ This sample shows how to implement an audio media app that provides
+ media library metadata and playback controls through a standard
+ service.
]]>
</intro>
</strings>
- <template src="base"/>
+ <!-- The basic templates have already been enabled. Uncomment more as desired. -->
+ <template src="basebuild" />
</sample>
diff --git a/ui/notifications/BasicNotifications/Application/.gitignore b/notification/BasicNotifications/Application/.gitignore
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/.gitignore
rename to notification/BasicNotifications/Application/.gitignore
diff --git a/ui/notifications/BasicNotifications/Application/proguard-project.txt b/notification/BasicNotifications/Application/proguard-project.txt
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/proguard-project.txt
rename to notification/BasicNotifications/Application/proguard-project.txt
diff --git a/ui/notifications/BasicNotifications/Application/src/main/AndroidManifest.xml b/notification/BasicNotifications/Application/src/main/AndroidManifest.xml
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/AndroidManifest.xml
rename to notification/BasicNotifications/Application/src/main/AndroidManifest.xml
diff --git a/ui/notifications/BasicNotifications/Application/src/main/java/com/example/android/basicnotifications/MainActivity.java b/notification/BasicNotifications/Application/src/main/java/com/example/android/basicnotifications/MainActivity.java
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/java/com/example/android/basicnotifications/MainActivity.java
rename to notification/BasicNotifications/Application/src/main/java/com/example/android/basicnotifications/MainActivity.java
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-hdpi-v11/ic_stat_notification.png b/notification/BasicNotifications/Application/src/main/res/drawable-hdpi-v11/ic_stat_notification.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-hdpi-v11/ic_stat_notification.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-hdpi-v11/ic_stat_notification.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-hdpi-v9/ic_stat_notification.png b/notification/BasicNotifications/Application/src/main/res/drawable-hdpi-v9/ic_stat_notification.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-hdpi-v9/ic_stat_notification.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-hdpi-v9/ic_stat_notification.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-hdpi/ic_launcher.png b/notification/BasicNotifications/Application/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-hdpi/ic_launcher.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-hdpi/ic_stat_notification.png b/notification/BasicNotifications/Application/src/main/res/drawable-hdpi/ic_stat_notification.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-hdpi/ic_stat_notification.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-hdpi/ic_stat_notification.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-ldpi-v11/ic_stat_notification.png b/notification/BasicNotifications/Application/src/main/res/drawable-ldpi-v11/ic_stat_notification.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-ldpi-v11/ic_stat_notification.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-ldpi-v11/ic_stat_notification.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-ldpi-v9/ic_stat_notification.png b/notification/BasicNotifications/Application/src/main/res/drawable-ldpi-v9/ic_stat_notification.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-ldpi-v9/ic_stat_notification.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-ldpi-v9/ic_stat_notification.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-mdpi-v11/ic_stat_notification.png b/notification/BasicNotifications/Application/src/main/res/drawable-mdpi-v11/ic_stat_notification.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-mdpi-v11/ic_stat_notification.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-mdpi-v11/ic_stat_notification.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-mdpi-v9/ic_stat_notification.png b/notification/BasicNotifications/Application/src/main/res/drawable-mdpi-v9/ic_stat_notification.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-mdpi-v9/ic_stat_notification.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-mdpi-v9/ic_stat_notification.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-mdpi/ic_launcher.png b/notification/BasicNotifications/Application/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-mdpi/ic_launcher.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-mdpi/ic_stat_notification.png b/notification/BasicNotifications/Application/src/main/res/drawable-mdpi/ic_stat_notification.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-mdpi/ic_stat_notification.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-mdpi/ic_stat_notification.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-xhdpi-v11/ic_stat_notification.png b/notification/BasicNotifications/Application/src/main/res/drawable-xhdpi-v11/ic_stat_notification.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-xhdpi-v11/ic_stat_notification.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-xhdpi-v11/ic_stat_notification.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-xhdpi-v9/ic_stat_notification.png b/notification/BasicNotifications/Application/src/main/res/drawable-xhdpi-v9/ic_stat_notification.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-xhdpi-v9/ic_stat_notification.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-xhdpi-v9/ic_stat_notification.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher.png b/notification/BasicNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-xhdpi/ic_stat_notification.png b/notification/BasicNotifications/Application/src/main/res/drawable-xhdpi/ic_stat_notification.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-xhdpi/ic_stat_notification.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-xhdpi/ic_stat_notification.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher.png b/notification/BasicNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to notification/BasicNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/layout/sample_layout.xml b/notification/BasicNotifications/Application/src/main/res/layout/sample_layout.xml
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/layout/sample_layout.xml
rename to notification/BasicNotifications/Application/src/main/res/layout/sample_layout.xml
diff --git a/ui/notifications/BasicNotifications/Application/src/main/res/values/strings.xml b/notification/BasicNotifications/Application/src/main/res/values/strings.xml
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/src/main/res/values/strings.xml
rename to notification/BasicNotifications/Application/src/main/res/values/strings.xml
diff --git a/ui/notifications/BasicNotifications/Application/tests/AndroidManifest.xml b/notification/BasicNotifications/Application/tests/AndroidManifest.xml
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/tests/AndroidManifest.xml
rename to notification/BasicNotifications/Application/tests/AndroidManifest.xml
diff --git a/ui/notifications/BasicNotifications/Application/tests/src/com/example/android/basicnotifications/tests/SampleTests.java b/notification/BasicNotifications/Application/tests/src/com/example/android/basicnotifications/tests/SampleTests.java
similarity index 100%
rename from ui/notifications/BasicNotifications/Application/tests/src/com/example/android/basicnotifications/tests/SampleTests.java
rename to notification/BasicNotifications/Application/tests/src/com/example/android/basicnotifications/tests/SampleTests.java
diff --git a/ui/notifications/BasicNotifications/CONTRIB.md b/notification/BasicNotifications/CONTRIB.md
similarity index 100%
rename from ui/notifications/BasicNotifications/CONTRIB.md
rename to notification/BasicNotifications/CONTRIB.md
diff --git a/ui/notifications/BasicNotifications/LICENSE b/notification/BasicNotifications/LICENSE
similarity index 100%
rename from ui/notifications/BasicNotifications/LICENSE
rename to notification/BasicNotifications/LICENSE
diff --git a/ui/notifications/BasicNotifications/README.md b/notification/BasicNotifications/README.md
similarity index 100%
rename from ui/notifications/BasicNotifications/README.md
rename to notification/BasicNotifications/README.md
diff --git a/notification/BasicNotifications/build.gradle b/notification/BasicNotifications/build.gradle
new file mode 100644
index 0000000..2b8d1ef
--- /dev/null
+++ b/notification/BasicNotifications/build.gradle
@@ -0,0 +1,11 @@
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../build"
+ pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/ui/notifications/BasicNotifications/buildSrc/build.gradle b/notification/BasicNotifications/buildSrc/build.gradle
similarity index 63%
copy from ui/notifications/BasicNotifications/buildSrc/build.gradle
copy to notification/BasicNotifications/buildSrc/build.gradle
index e344a8c..8c294c2 100644
--- a/ui/notifications/BasicNotifications/buildSrc/build.gradle
+++ b/notification/BasicNotifications/buildSrc/build.gradle
@@ -1,6 +1,3 @@
-
-
-
repositories {
mavenCentral()
}
@@ -11,7 +8,7 @@
sourceSets {
main {
groovy {
- srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
+ srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
}
}
}
diff --git a/ui/notifications/BasicNotifications/gradle/wrapper/gradle-wrapper.jar b/notification/BasicNotifications/gradle/wrapper/gradle-wrapper.jar
similarity index 100%
rename from ui/notifications/BasicNotifications/gradle/wrapper/gradle-wrapper.jar
rename to notification/BasicNotifications/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/ui/notifications/BasicNotifications/gradle/wrapper/gradle-wrapper.properties b/notification/BasicNotifications/gradle/wrapper/gradle-wrapper.properties
similarity index 100%
rename from ui/notifications/BasicNotifications/gradle/wrapper/gradle-wrapper.properties
rename to notification/BasicNotifications/gradle/wrapper/gradle-wrapper.properties
diff --git a/ui/notifications/BasicNotifications/gradlew b/notification/BasicNotifications/gradlew
similarity index 100%
rename from ui/notifications/BasicNotifications/gradlew
rename to notification/BasicNotifications/gradlew
diff --git a/ui/notifications/BasicNotifications/gradlew.bat b/notification/BasicNotifications/gradlew.bat
similarity index 100%
rename from ui/notifications/BasicNotifications/gradlew.bat
rename to notification/BasicNotifications/gradlew.bat
diff --git a/ui/notifications/BasicNotifications/packaging.yaml b/notification/BasicNotifications/packaging.yaml
similarity index 100%
rename from ui/notifications/BasicNotifications/packaging.yaml
rename to notification/BasicNotifications/packaging.yaml
diff --git a/ui/notifications/BasicNotifications/settings.gradle b/notification/BasicNotifications/settings.gradle
similarity index 100%
rename from ui/notifications/BasicNotifications/settings.gradle
rename to notification/BasicNotifications/settings.gradle
diff --git a/ui/notifications/BasicNotifications/template-params.xml b/notification/BasicNotifications/template-params.xml
similarity index 96%
rename from ui/notifications/BasicNotifications/template-params.xml
rename to notification/BasicNotifications/template-params.xml
index 7118c24..924a3d4 100644
--- a/ui/notifications/BasicNotifications/template-params.xml
+++ b/notification/BasicNotifications/template-params.xml
@@ -17,7 +17,7 @@
<sample>
<name>BasicNotifications</name>
- <group>UI</group>
+ <group>Notification</group>
<package>com.example.android.basicnotifications</package>
<!-- change minSdk if needed-->
<minSdk>8</minSdk>
diff --git a/ui/notifications/CustomNotifications/Application/.gitignore b/notification/CustomNotifications/Application/.gitignore
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/.gitignore
rename to notification/CustomNotifications/Application/.gitignore
diff --git a/ui/notifications/CustomNotifications/Application/proguard-project.txt b/notification/CustomNotifications/Application/proguard-project.txt
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/proguard-project.txt
rename to notification/CustomNotifications/Application/proguard-project.txt
diff --git a/ui/notifications/CustomNotifications/Application/src/main/AndroidManifest.xml b/notification/CustomNotifications/Application/src/main/AndroidManifest.xml
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/AndroidManifest.xml
rename to notification/CustomNotifications/Application/src/main/AndroidManifest.xml
diff --git a/ui/notifications/CustomNotifications/Application/src/main/java/com/example/android/customnotifications/MainActivity.java b/notification/CustomNotifications/Application/src/main/java/com/example/android/customnotifications/MainActivity.java
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/java/com/example/android/customnotifications/MainActivity.java
rename to notification/CustomNotifications/Application/src/main/java/com/example/android/customnotifications/MainActivity.java
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-hdpi-v11/ic_stat_custom.png b/notification/CustomNotifications/Application/src/main/res/drawable-hdpi-v11/ic_stat_custom.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-hdpi-v11/ic_stat_custom.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-hdpi-v11/ic_stat_custom.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-hdpi-v9/ic_stat_custom.png b/notification/CustomNotifications/Application/src/main/res/drawable-hdpi-v9/ic_stat_custom.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-hdpi-v9/ic_stat_custom.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-hdpi-v9/ic_stat_custom.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-hdpi/ic_launcher.png b/notification/CustomNotifications/Application/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-hdpi/ic_launcher.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-hdpi/ic_stat_custom.png b/notification/CustomNotifications/Application/src/main/res/drawable-hdpi/ic_stat_custom.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-hdpi/ic_stat_custom.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-hdpi/ic_stat_custom.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-ldpi-v11/ic_stat_custom.png b/notification/CustomNotifications/Application/src/main/res/drawable-ldpi-v11/ic_stat_custom.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-ldpi-v11/ic_stat_custom.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-ldpi-v11/ic_stat_custom.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-ldpi-v9/ic_stat_custom.png b/notification/CustomNotifications/Application/src/main/res/drawable-ldpi-v9/ic_stat_custom.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-ldpi-v9/ic_stat_custom.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-ldpi-v9/ic_stat_custom.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-mdpi-v11/ic_stat_custom.png b/notification/CustomNotifications/Application/src/main/res/drawable-mdpi-v11/ic_stat_custom.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-mdpi-v11/ic_stat_custom.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-mdpi-v11/ic_stat_custom.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-mdpi-v9/ic_stat_custom.png b/notification/CustomNotifications/Application/src/main/res/drawable-mdpi-v9/ic_stat_custom.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-mdpi-v9/ic_stat_custom.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-mdpi-v9/ic_stat_custom.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-mdpi/ic_launcher.png b/notification/CustomNotifications/Application/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-mdpi/ic_launcher.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-mdpi/ic_stat_custom.png b/notification/CustomNotifications/Application/src/main/res/drawable-mdpi/ic_stat_custom.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-mdpi/ic_stat_custom.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-mdpi/ic_stat_custom.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-xhdpi-v11/ic_stat_custom.png b/notification/CustomNotifications/Application/src/main/res/drawable-xhdpi-v11/ic_stat_custom.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-xhdpi-v11/ic_stat_custom.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-xhdpi-v11/ic_stat_custom.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-xhdpi-v9/ic_stat_custom.png b/notification/CustomNotifications/Application/src/main/res/drawable-xhdpi-v9/ic_stat_custom.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-xhdpi-v9/ic_stat_custom.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-xhdpi-v9/ic_stat_custom.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher.png b/notification/CustomNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-xhdpi/ic_stat_custom.png b/notification/CustomNotifications/Application/src/main/res/drawable-xhdpi/ic_stat_custom.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-xhdpi/ic_stat_custom.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-xhdpi/ic_stat_custom.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-xhdpi/robot.png b/notification/CustomNotifications/Application/src/main/res/drawable-xhdpi/robot.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-xhdpi/robot.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-xhdpi/robot.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-xhdpi/robot_expanded.png b/notification/CustomNotifications/Application/src/main/res/drawable-xhdpi/robot_expanded.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-xhdpi/robot_expanded.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-xhdpi/robot_expanded.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher.png b/notification/CustomNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to notification/CustomNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/layout/notification.xml b/notification/CustomNotifications/Application/src/main/res/layout/notification.xml
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/layout/notification.xml
rename to notification/CustomNotifications/Application/src/main/res/layout/notification.xml
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/layout/notification_expanded.xml b/notification/CustomNotifications/Application/src/main/res/layout/notification_expanded.xml
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/layout/notification_expanded.xml
rename to notification/CustomNotifications/Application/src/main/res/layout/notification_expanded.xml
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/layout/sample_main.xml b/notification/CustomNotifications/Application/src/main/res/layout/sample_main.xml
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/layout/sample_main.xml
rename to notification/CustomNotifications/Application/src/main/res/layout/sample_main.xml
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/values-sw720dp-land/dimens.xml b/notification/CustomNotifications/Application/src/main/res/values-sw720dp-land/dimens.xml
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/values-sw720dp-land/dimens.xml
rename to notification/CustomNotifications/Application/src/main/res/values-sw720dp-land/dimens.xml
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/values-v9/styles.xml b/notification/CustomNotifications/Application/src/main/res/values-v9/styles.xml
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/values-v9/styles.xml
rename to notification/CustomNotifications/Application/src/main/res/values-v9/styles.xml
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/values/dimens.xml b/notification/CustomNotifications/Application/src/main/res/values/dimens.xml
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/values/dimens.xml
rename to notification/CustomNotifications/Application/src/main/res/values/dimens.xml
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/values/strings.xml b/notification/CustomNotifications/Application/src/main/res/values/strings.xml
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/values/strings.xml
rename to notification/CustomNotifications/Application/src/main/res/values/strings.xml
diff --git a/ui/notifications/CustomNotifications/Application/src/main/res/values/styles.xml b/notification/CustomNotifications/Application/src/main/res/values/styles.xml
similarity index 100%
rename from ui/notifications/CustomNotifications/Application/src/main/res/values/styles.xml
rename to notification/CustomNotifications/Application/src/main/res/values/styles.xml
diff --git a/ui/notifications/CustomNotifications/CONTRIB.md b/notification/CustomNotifications/CONTRIB.md
similarity index 100%
rename from ui/notifications/CustomNotifications/CONTRIB.md
rename to notification/CustomNotifications/CONTRIB.md
diff --git a/ui/notifications/CustomNotifications/LICENSE b/notification/CustomNotifications/LICENSE
similarity index 100%
rename from ui/notifications/CustomNotifications/LICENSE
rename to notification/CustomNotifications/LICENSE
diff --git a/ui/notifications/CustomNotifications/README.md b/notification/CustomNotifications/README.md
similarity index 100%
rename from ui/notifications/CustomNotifications/README.md
rename to notification/CustomNotifications/README.md
diff --git a/notification/CustomNotifications/build.gradle b/notification/CustomNotifications/build.gradle
new file mode 100644
index 0000000..2b8d1ef
--- /dev/null
+++ b/notification/CustomNotifications/build.gradle
@@ -0,0 +1,11 @@
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../build"
+ pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/ui/notifications/BasicNotifications/buildSrc/build.gradle b/notification/CustomNotifications/buildSrc/build.gradle
similarity index 63%
copy from ui/notifications/BasicNotifications/buildSrc/build.gradle
copy to notification/CustomNotifications/buildSrc/build.gradle
index e344a8c..8c294c2 100644
--- a/ui/notifications/BasicNotifications/buildSrc/build.gradle
+++ b/notification/CustomNotifications/buildSrc/build.gradle
@@ -1,6 +1,3 @@
-
-
-
repositories {
mavenCentral()
}
@@ -11,7 +8,7 @@
sourceSets {
main {
groovy {
- srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
+ srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
}
}
}
diff --git a/ui/notifications/CustomNotifications/gradle/wrapper/gradle-wrapper.jar b/notification/CustomNotifications/gradle/wrapper/gradle-wrapper.jar
similarity index 100%
rename from ui/notifications/CustomNotifications/gradle/wrapper/gradle-wrapper.jar
rename to notification/CustomNotifications/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/ui/notifications/CustomNotifications/gradle/wrapper/gradle-wrapper.properties b/notification/CustomNotifications/gradle/wrapper/gradle-wrapper.properties
similarity index 100%
rename from ui/notifications/CustomNotifications/gradle/wrapper/gradle-wrapper.properties
rename to notification/CustomNotifications/gradle/wrapper/gradle-wrapper.properties
diff --git a/ui/notifications/CustomNotifications/gradlew b/notification/CustomNotifications/gradlew
similarity index 100%
rename from ui/notifications/CustomNotifications/gradlew
rename to notification/CustomNotifications/gradlew
diff --git a/ui/notifications/CustomNotifications/gradlew.bat b/notification/CustomNotifications/gradlew.bat
similarity index 100%
rename from ui/notifications/CustomNotifications/gradlew.bat
rename to notification/CustomNotifications/gradlew.bat
diff --git a/ui/notifications/CustomNotifications/packaging.yaml b/notification/CustomNotifications/packaging.yaml
similarity index 100%
rename from ui/notifications/CustomNotifications/packaging.yaml
rename to notification/CustomNotifications/packaging.yaml
diff --git a/ui/notifications/CustomNotifications/settings.gradle b/notification/CustomNotifications/settings.gradle
similarity index 100%
rename from ui/notifications/CustomNotifications/settings.gradle
rename to notification/CustomNotifications/settings.gradle
diff --git a/ui/notifications/CustomNotifications/template-params.xml b/notification/CustomNotifications/template-params.xml
similarity index 96%
rename from ui/notifications/CustomNotifications/template-params.xml
rename to notification/CustomNotifications/template-params.xml
index ff90ca3..1ded0d1 100644
--- a/ui/notifications/CustomNotifications/template-params.xml
+++ b/notification/CustomNotifications/template-params.xml
@@ -16,7 +16,7 @@
-->
<sample>
<name>CustomNotifications</name>
- <group>UI</group>
+ <group>Notification</group>
<package>com.example.android.customnotifications</package>
<!-- change minSdk if needed-->
diff --git a/ui/notifications/LNotifications/Application/.gitignore b/notification/LNotifications/Application/.gitignore
similarity index 100%
rename from ui/notifications/LNotifications/Application/.gitignore
rename to notification/LNotifications/Application/.gitignore
diff --git a/ui/notifications/LNotifications/Application/proguard-project.txt b/notification/LNotifications/Application/proguard-project.txt
similarity index 100%
rename from ui/notifications/LNotifications/Application/proguard-project.txt
rename to notification/LNotifications/Application/proguard-project.txt
diff --git a/ui/notifications/LNotifications/Application/src/main/AndroidManifest.xml b/notification/LNotifications/Application/src/main/AndroidManifest.xml
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/AndroidManifest.xml
rename to notification/LNotifications/Application/src/main/AndroidManifest.xml
diff --git a/ui/notifications/LNotifications/Application/src/main/java/com/example/android/lnotifications/HeadsUpNotificationFragment.java b/notification/LNotifications/Application/src/main/java/com/example/android/lnotifications/HeadsUpNotificationFragment.java
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/java/com/example/android/lnotifications/HeadsUpNotificationFragment.java
rename to notification/LNotifications/Application/src/main/java/com/example/android/lnotifications/HeadsUpNotificationFragment.java
diff --git a/ui/notifications/LNotifications/Application/src/main/java/com/example/android/lnotifications/LNotificationActivity.java b/notification/LNotifications/Application/src/main/java/com/example/android/lnotifications/LNotificationActivity.java
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/java/com/example/android/lnotifications/LNotificationActivity.java
rename to notification/LNotifications/Application/src/main/java/com/example/android/lnotifications/LNotificationActivity.java
diff --git a/ui/notifications/LNotifications/Application/src/main/java/com/example/android/lnotifications/OtherMetadataFragment.java b/notification/LNotifications/Application/src/main/java/com/example/android/lnotifications/OtherMetadataFragment.java
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/java/com/example/android/lnotifications/OtherMetadataFragment.java
rename to notification/LNotifications/Application/src/main/java/com/example/android/lnotifications/OtherMetadataFragment.java
diff --git a/ui/notifications/LNotifications/Application/src/main/java/com/example/android/lnotifications/VisibilityMetadataFragment.java b/notification/LNotifications/Application/src/main/java/com/example/android/lnotifications/VisibilityMetadataFragment.java
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/java/com/example/android/lnotifications/VisibilityMetadataFragment.java
rename to notification/LNotifications/Application/src/main/java/com/example/android/lnotifications/VisibilityMetadataFragment.java
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_contact_picture.png b/notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_contact_picture.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_contact_picture.png
rename to notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_contact_picture.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_launcher.png b/notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_launcher.png
rename to notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_launcher_notification.png b/notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_launcher_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_launcher_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_launcher_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_private.png b/notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_private.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_private.png
rename to notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_private.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_private_notification.png b/notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_private_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_private_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_private_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_public.png b/notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_public.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_public.png
rename to notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_public.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_public_notification.png b/notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_public_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_public_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_public_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_secret.png b/notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_secret.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_secret.png
rename to notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_secret.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_secret_notification.png b/notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_secret_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_secret_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-hdpi/ic_secret_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_contact_picture.png b/notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_contact_picture.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_contact_picture.png
rename to notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_contact_picture.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_launcher.png b/notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_launcher.png
rename to notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_launcher_notification.png b/notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_launcher_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_launcher_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_launcher_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_private.png b/notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_private.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_private.png
rename to notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_private.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_private_notification.png b/notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_private_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_private_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_private_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_public.png b/notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_public.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_public.png
rename to notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_public.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_public_notification.png b/notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_public_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_public_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_public_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_secret.png b/notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_secret.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_secret.png
rename to notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_secret.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_secret_notification.png b/notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_secret_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_secret_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-mdpi/ic_secret_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_contact_picture.png b/notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_contact_picture.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_contact_picture.png
rename to notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_contact_picture.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher.png b/notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher.png
rename to notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher_notification.png b/notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_launcher_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_private.png b/notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_private.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_private.png
rename to notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_private.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_private_notification.png b/notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_private_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_private_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_private_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_public.png b/notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_public.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_public.png
rename to notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_public.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_public_notification.png b/notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_public_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_public_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_public_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_secret.png b/notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_secret.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_secret.png
rename to notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_secret.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_secret_notification.png b/notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_secret_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_secret_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-xhdpi/ic_secret_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_contact_picture.png b/notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_contact_picture.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_contact_picture.png
rename to notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_contact_picture.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher.png b/notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher_notification.png b/notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_launcher_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_private.png b/notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_private.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_private.png
rename to notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_private.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_private_notification.png b/notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_private_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_private_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_private_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_public.png b/notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_public.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_public.png
rename to notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_public.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_public_notification.png b/notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_public_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_public_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_public_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_secret.png b/notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_secret.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_secret.png
rename to notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_secret.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_secret_notification.png b/notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_secret_notification.png
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_secret_notification.png
rename to notification/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_secret_notification.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/layout/activity_notification.xml b/notification/LNotifications/Application/src/main/res/layout/activity_notification.xml
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/layout/activity_notification.xml
rename to notification/LNotifications/Application/src/main/res/layout/activity_notification.xml
diff --git a/ui/notifications/LNotifications/Application/src/main/res/layout/contact_entry.xml b/notification/LNotifications/Application/src/main/res/layout/contact_entry.xml
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/layout/contact_entry.xml
rename to notification/LNotifications/Application/src/main/res/layout/contact_entry.xml
diff --git a/ui/notifications/LNotifications/Application/src/main/res/layout/fragment_heads_up_notification.xml b/notification/LNotifications/Application/src/main/res/layout/fragment_heads_up_notification.xml
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/layout/fragment_heads_up_notification.xml
rename to notification/LNotifications/Application/src/main/res/layout/fragment_heads_up_notification.xml
diff --git a/ui/notifications/LNotifications/Application/src/main/res/layout/fragment_other_metadata.xml b/notification/LNotifications/Application/src/main/res/layout/fragment_other_metadata.xml
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/layout/fragment_other_metadata.xml
rename to notification/LNotifications/Application/src/main/res/layout/fragment_other_metadata.xml
diff --git a/ui/notifications/LNotifications/Application/src/main/res/layout/fragment_visibility_metadata_notification.xml b/notification/LNotifications/Application/src/main/res/layout/fragment_visibility_metadata_notification.xml
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/layout/fragment_visibility_metadata_notification.xml
rename to notification/LNotifications/Application/src/main/res/layout/fragment_visibility_metadata_notification.xml
diff --git a/ui/notifications/LNotifications/Application/src/main/res/values/colors.xml b/notification/LNotifications/Application/src/main/res/values/colors.xml
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/values/colors.xml
rename to notification/LNotifications/Application/src/main/res/values/colors.xml
diff --git a/ui/notifications/LNotifications/Application/src/main/res/values/dimens.xml b/notification/LNotifications/Application/src/main/res/values/dimens.xml
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/values/dimens.xml
rename to notification/LNotifications/Application/src/main/res/values/dimens.xml
diff --git a/ui/notifications/LNotifications/Application/src/main/res/values/strings.xml b/notification/LNotifications/Application/src/main/res/values/strings.xml
similarity index 100%
rename from ui/notifications/LNotifications/Application/src/main/res/values/strings.xml
rename to notification/LNotifications/Application/src/main/res/values/strings.xml
diff --git a/ui/notifications/LNotifications/Application/tests/AndroidManifest.xml b/notification/LNotifications/Application/tests/AndroidManifest.xml
similarity index 100%
rename from ui/notifications/LNotifications/Application/tests/AndroidManifest.xml
rename to notification/LNotifications/Application/tests/AndroidManifest.xml
diff --git a/ui/notifications/LNotifications/Application/tests/src/com/example/android/lnotifications/HeadsUpNotificationFragmentTest.java b/notification/LNotifications/Application/tests/src/com/example/android/lnotifications/HeadsUpNotificationFragmentTest.java
similarity index 100%
rename from ui/notifications/LNotifications/Application/tests/src/com/example/android/lnotifications/HeadsUpNotificationFragmentTest.java
rename to notification/LNotifications/Application/tests/src/com/example/android/lnotifications/HeadsUpNotificationFragmentTest.java
diff --git a/ui/notifications/LNotifications/Application/tests/src/com/example/android/lnotifications/LNotificationActivityTest.java b/notification/LNotifications/Application/tests/src/com/example/android/lnotifications/LNotificationActivityTest.java
similarity index 100%
rename from ui/notifications/LNotifications/Application/tests/src/com/example/android/lnotifications/LNotificationActivityTest.java
rename to notification/LNotifications/Application/tests/src/com/example/android/lnotifications/LNotificationActivityTest.java
diff --git a/ui/notifications/LNotifications/Application/tests/src/com/example/android/lnotifications/OtherMetadataFragmentTest.java b/notification/LNotifications/Application/tests/src/com/example/android/lnotifications/OtherMetadataFragmentTest.java
similarity index 100%
rename from ui/notifications/LNotifications/Application/tests/src/com/example/android/lnotifications/OtherMetadataFragmentTest.java
rename to notification/LNotifications/Application/tests/src/com/example/android/lnotifications/OtherMetadataFragmentTest.java
diff --git a/ui/notifications/LNotifications/Application/tests/src/com/example/android/lnotifications/VisibilityMetadataFragmentTest.java b/notification/LNotifications/Application/tests/src/com/example/android/lnotifications/VisibilityMetadataFragmentTest.java
similarity index 100%
rename from ui/notifications/LNotifications/Application/tests/src/com/example/android/lnotifications/VisibilityMetadataFragmentTest.java
rename to notification/LNotifications/Application/tests/src/com/example/android/lnotifications/VisibilityMetadataFragmentTest.java
diff --git a/ui/notifications/BasicNotifications/CONTRIB.md b/notification/LNotifications/CONTRIB.md
similarity index 100%
copy from ui/notifications/BasicNotifications/CONTRIB.md
copy to notification/LNotifications/CONTRIB.md
diff --git a/ui/notifications/BasicNotifications/LICENSE b/notification/LNotifications/LICENSE
similarity index 100%
copy from ui/notifications/BasicNotifications/LICENSE
copy to notification/LNotifications/LICENSE
diff --git a/ui/notifications/LNotifications/README.md b/notification/LNotifications/README.md
similarity index 100%
rename from ui/notifications/LNotifications/README.md
rename to notification/LNotifications/README.md
diff --git a/notification/LNotifications/build.gradle b/notification/LNotifications/build.gradle
new file mode 100644
index 0000000..2b8d1ef
--- /dev/null
+++ b/notification/LNotifications/build.gradle
@@ -0,0 +1,11 @@
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../build"
+ pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/ui/notifications/BasicNotifications/buildSrc/build.gradle b/notification/LNotifications/buildSrc/build.gradle
similarity index 63%
copy from ui/notifications/BasicNotifications/buildSrc/build.gradle
copy to notification/LNotifications/buildSrc/build.gradle
index e344a8c..8c294c2 100644
--- a/ui/notifications/BasicNotifications/buildSrc/build.gradle
+++ b/notification/LNotifications/buildSrc/build.gradle
@@ -1,6 +1,3 @@
-
-
-
repositories {
mavenCentral()
}
@@ -11,7 +8,7 @@
sourceSets {
main {
groovy {
- srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
+ srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
}
}
}
diff --git a/ui/notifications/LNotifications/gradle/wrapper/gradle-wrapper.jar b/notification/LNotifications/gradle/wrapper/gradle-wrapper.jar
similarity index 100%
rename from ui/notifications/LNotifications/gradle/wrapper/gradle-wrapper.jar
rename to notification/LNotifications/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/ui/notifications/LNotifications/gradle/wrapper/gradle-wrapper.properties b/notification/LNotifications/gradle/wrapper/gradle-wrapper.properties
similarity index 100%
rename from ui/notifications/LNotifications/gradle/wrapper/gradle-wrapper.properties
rename to notification/LNotifications/gradle/wrapper/gradle-wrapper.properties
diff --git a/ui/notifications/LNotifications/gradlew b/notification/LNotifications/gradlew
similarity index 100%
rename from ui/notifications/LNotifications/gradlew
rename to notification/LNotifications/gradlew
diff --git a/ui/notifications/LNotifications/gradlew.bat b/notification/LNotifications/gradlew.bat
similarity index 100%
rename from ui/notifications/LNotifications/gradlew.bat
rename to notification/LNotifications/gradlew.bat
diff --git a/ui/notifications/LNotifications/packaging.yaml b/notification/LNotifications/packaging.yaml
similarity index 100%
rename from ui/notifications/LNotifications/packaging.yaml
rename to notification/LNotifications/packaging.yaml
diff --git a/ui/notifications/LNotifications/settings.gradle b/notification/LNotifications/settings.gradle
similarity index 100%
rename from ui/notifications/LNotifications/settings.gradle
rename to notification/LNotifications/settings.gradle
diff --git a/ui/notifications/LNotifications/template-params.xml b/notification/LNotifications/template-params.xml
similarity index 97%
rename from ui/notifications/LNotifications/template-params.xml
rename to notification/LNotifications/template-params.xml
index 43d5dc0..6257c81 100644
--- a/ui/notifications/LNotifications/template-params.xml
+++ b/notification/LNotifications/template-params.xml
@@ -17,7 +17,7 @@
<sample>
<name>LNotifications Sample</name>
- <group>UI</group>
+ <group>Notification</group>
<package>com.example.android.lnotifications</package>
<!-- change minSdk if needed-->
diff --git a/ui/notifications/LNotifications/Application/.gitignore b/notification/MessagingService/Application/.gitignore
similarity index 100%
copy from ui/notifications/LNotifications/Application/.gitignore
copy to notification/MessagingService/Application/.gitignore
diff --git a/notification/MessagingService/Application/libs/android-auto-sdk.jar b/notification/MessagingService/Application/libs/android-auto-sdk.jar
new file mode 100644
index 0000000..8a9897d
--- /dev/null
+++ b/notification/MessagingService/Application/libs/android-auto-sdk.jar
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/proguard-project.txt b/notification/MessagingService/Application/proguard-project.txt
similarity index 100%
copy from ui/notifications/LNotifications/Application/proguard-project.txt
copy to notification/MessagingService/Application/proguard-project.txt
diff --git a/notification/MessagingService/Application/src/androidTest/java/com/example/android/messagingservice/test/SampleTests.java b/notification/MessagingService/Application/src/androidTest/java/com/example/android/messagingservice/test/SampleTests.java
new file mode 100644
index 0000000..79d618b
--- /dev/null
+++ b/notification/MessagingService/Application/src/androidTest/java/com/example/android/messagingservice/test/SampleTests.java
@@ -0,0 +1,76 @@
+/*
+* Copyright 2013 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+/*
+* Copyright (C) 2014 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.example.android.messagingservice.test;
+
+import com.example.android.messagingservice.*;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+* Tests for MessagingService sample.
+*/
+public class SampleTests extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private MainActivity mTestActivity;
+ private MessagingServiceFragment mTestFragment;
+
+ public SampleTests() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // Starts the activity under test using the default Intent with:
+ // action = {@link Intent#ACTION_MAIN}
+ // flags = {@link Intent#FLAG_ACTIVITY_NEW_TASK}
+ // All other fields are null or empty.
+ mTestActivity = getActivity();
+ mTestFragment = (MessagingServiceFragment)
+ mTestActivity.getSupportFragmentManager().getFragments().get(1);
+ }
+
+ /**
+ * Test if the test fixture has been set up correctly.
+ */
+ public void testPreconditions() {
+ //Try to add a message to add context to your assertions. These messages will be shown if
+ //a tests fails and make it easy to understand why a test failed
+ assertNotNull("mTestActivity is null", mTestActivity);
+ assertNotNull("mTestFragment is null", mTestFragment);
+ }
+
+ /**
+ * Add more tests below.
+ */
+
+}
diff --git a/notification/MessagingService/Application/src/main/AndroidManifest.xml b/notification/MessagingService/Application/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f8a5850
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/AndroidManifest.xml
@@ -0,0 +1,52 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.messagingservice">
+
+ <application android:allowBackup="true"
+ android:label="@string/app_name"
+ android:icon="@drawable/ic_launcher"
+ android:theme="@style/AppTheme">
+
+ <meta-data android:name="com.google.android.gms.car.application"
+ android:resource="@xml/automotive_app_desc"/>
+
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <service android:name=".MessagingService">
+ </service>
+
+ <receiver android:name=".MessageReadReceiver">
+ <intent-filter>
+ <action android:name="com.example.android.messagingservice.ACTION_MESSAGE_READ"/>
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name=".MessageReplyReceiver">
+ <intent-filter>
+ <action android:name="com.example.android.messagingservice.ACTION_MESSAGE_REPLY"/>
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/Conversations.java b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/Conversations.java
new file mode 100644
index 0000000..210e061
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/Conversations.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.messagingservice;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * A simple class that denotes unread conversations and messages. In a real world application,
+ * this would be replaced by a content provider that actually gets the unread messages to be
+ * shown to the user.
+ */
+public class Conversations {
+
+ /**
+ * Set of strings used as messages by the sample.
+ */
+ private static final String[] MESSAGES = new String[]{
+ "Are you at home?",
+ "Can you give me a call?",
+ "Hey yt?",
+ "Don't forget to get some milk on your way back home",
+ "Is that project done?",
+ "Did you finish the Messaging app yet?"
+ };
+
+ /**
+ * Senders of the said messages.
+ */
+ private static final String[] PARTICIPANTS = new String[]{
+ "John Rambo",
+ "Han Solo",
+ "Rocky Balboa",
+ "Lara Croft"
+ };
+
+ static class Conversation {
+
+ private final int conversationId;
+
+ private final String participantName;
+
+ /**
+ * A given conversation can have a single or multiple messages.
+ * Note that the messages are sorted from *newest* to *oldest*
+ */
+ private final List<String> messages;
+
+ private final long timestamp;
+
+ public Conversation(int conversationId, String participantName,
+ List<String> messages) {
+ this.conversationId = conversationId;
+ this.participantName = participantName;
+ this.messages = messages == null ? Collections.<String>emptyList() : messages;
+ this.timestamp = System.currentTimeMillis();
+ }
+
+ public int getConversationId() {
+ return conversationId;
+ }
+
+ public String getParticipantName() {
+ return participantName;
+ }
+
+ public List<String> getMessages() {
+ return messages;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public String toString() {
+ return "[Conversation: conversationId=" + conversationId +
+ ", participantName=" + participantName +
+ ", messages=" + messages +
+ ", timestamp=" + timestamp + "]";
+ }
+ }
+
+ private Conversations() {
+ }
+
+ public static Conversation[] getUnreadConversations(int howManyConversations,
+ int messagesPerConversation) {
+ Conversation[] conversations = new Conversation[howManyConversations];
+ for (int i = 0; i < howManyConversations; i++) {
+ conversations[i] = new Conversation(
+ ThreadLocalRandom.current().nextInt(),
+ name(), makeMessages(messagesPerConversation));
+ }
+ return conversations;
+ }
+
+ private static List<String> makeMessages(int messagesPerConversation) {
+ int maxLen = MESSAGES.length;
+ List<String> messages = new ArrayList<>(messagesPerConversation);
+ for (int i = 0; i < messagesPerConversation; i++) {
+ messages.add(MESSAGES[ThreadLocalRandom.current().nextInt(0, maxLen)]);
+ }
+ return messages;
+ }
+
+ private static String name() {
+ return PARTICIPANTS[ThreadLocalRandom.current().nextInt(0, PARTICIPANTS.length)];
+ }
+}
diff --git a/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MainActivity.java b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MainActivity.java
new file mode 100644
index 0000000..e558a64
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MainActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.messagingservice;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ if (savedInstanceState == null) {
+ getFragmentManager().beginTransaction()
+ .add(R.id.container, new MessagingFragment())
+ .commit();
+ }
+ }
+}
diff --git a/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessageLogger.java b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessageLogger.java
new file mode 100644
index 0000000..d1007b5
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessageLogger.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.messagingservice;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * A simple logger that uses shared preferences to log messages, their reads
+ * and replies. Don't use this in a real world application. This logger is only
+ * used for displaying the messages in the text view.
+ */
+public class MessageLogger {
+
+ private static final String PREF_MESSAGE = "MESSAGE_LOGGER";
+ private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ public static final String LOG_KEY = "message_data";
+ public static final String LINE_BREAKS = "\n\n";
+
+ public static void logMessage(Context context, String message) {
+ SharedPreferences prefs = getPrefs(context);
+ message = DATE_FORMAT.format(new Date(System.currentTimeMillis())) + ": " + message;
+ prefs.edit()
+ .putString(LOG_KEY, prefs.getString(LOG_KEY, "") + LINE_BREAKS + message)
+ .apply();
+ }
+
+ public static SharedPreferences getPrefs(Context context) {
+ return context.getSharedPreferences(PREF_MESSAGE, Context.MODE_PRIVATE);
+ }
+
+ public static String getAllMessages(Context context) {
+ return getPrefs(context).getString(LOG_KEY, "");
+ }
+
+ public static void clear(Context context) {
+ getPrefs(context).edit().remove(LOG_KEY).apply();
+ }
+}
diff --git a/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessageReadReceiver.java b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessageReadReceiver.java
new file mode 100644
index 0000000..f28a3a7
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessageReadReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.messagingservice;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.support.v4.app.NotificationManagerCompat;
+import android.util.Log;
+
+public class MessageReadReceiver extends BroadcastReceiver {
+ private static final String TAG = MessageReadReceiver.class.getSimpleName();
+
+ private static final String CONVERSATION_ID = "conversation_id";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive");
+ int conversationId = intent.getIntExtra(CONVERSATION_ID, -1);
+ if (conversationId != -1) {
+ Log.d(TAG, "Conversation " + conversationId + " was read");
+ MessageLogger.logMessage(context, "Conversation " + conversationId + " was read.");
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
+ notificationManager.cancel(conversationId);
+ }
+ }
+}
diff --git a/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessageReplyReceiver.java b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessageReplyReceiver.java
new file mode 100644
index 0000000..0a3eba6
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessageReplyReceiver.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.messagingservice;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.RemoteInput;
+import android.util.Log;
+
+/**
+ * A receiver that gets called when a reply is sent to a given conversationId
+ */
+public class MessageReplyReceiver extends BroadcastReceiver {
+
+ private static final String TAG = MessageReplyReceiver.class.getSimpleName();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (MessagingService.REPLY_ACTION.equals(intent.getAction())) {
+ int conversationId = intent.getIntExtra(MessagingService.CONVERSATION_ID, -1);
+ CharSequence reply = getMessageText(intent);
+ if (conversationId != -1) {
+ Log.d(TAG, "Got reply (" + reply + ") for ConversationId " + conversationId);
+ MessageLogger.logMessage(context, "ConversationId: " + conversationId +
+ " received a reply: [" + reply + "]");
+ }
+ }
+ }
+
+ /**
+ * Get the message text from the intent.
+ * Note that you should call {@code RemoteInput#getResultsFromIntent(intent)} to process
+ * the RemoteInput.
+ */
+ private CharSequence getMessageText(Intent intent) {
+ Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+ if (remoteInput != null) {
+ return remoteInput.getCharSequence(MessagingService.EXTRA_VOICE_REPLY);
+ }
+ return null;
+ }
+}
diff --git a/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessagingFragment.java b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessagingFragment.java
new file mode 100644
index 0000000..f8efcc0
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessagingFragment.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.messagingservice;
+
+import android.app.Fragment;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * The main fragment that shows the buttons and the text view containing the log.
+ */
+public class MessagingFragment extends Fragment implements View.OnClickListener {
+
+ private static final String TAG = MessagingFragment.class.getSimpleName();
+
+ private Button mSendSingleConversation;
+ private Button mSendTwoConversations;
+ private Button mSendConversationWithThreeMessages;
+ private TextView mDataPortView;
+ private Button mClearLogButton;
+
+ private Messenger mService;
+ private boolean mBound;
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder service) {
+ mService = new Messenger(service);
+ mBound = true;
+ setButtonsState(true);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ mService = null;
+ mBound = false;
+ setButtonsState(false);
+ }
+ };
+
+ private SharedPreferences.OnSharedPreferenceChangeListener listener =
+ new SharedPreferences.OnSharedPreferenceChangeListener() {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (MessageLogger.LOG_KEY.equals(key)) {
+ mDataPortView.setText(MessageLogger.getAllMessages(getActivity()));
+ }
+ }
+ };
+
+ public MessagingFragment() {
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_message_me, container, false);
+
+ mSendSingleConversation = (Button) rootView.findViewById(R.id.send_1_conversation);
+ mSendSingleConversation.setOnClickListener(this);
+
+ mSendTwoConversations = (Button) rootView.findViewById(R.id.send_2_conversations);
+ mSendTwoConversations.setOnClickListener(this);
+
+ mSendConversationWithThreeMessages =
+ (Button) rootView.findViewById(R.id.send_1_conversation_3_messages);
+ mSendConversationWithThreeMessages.setOnClickListener(this);
+
+ mDataPortView = (TextView) rootView.findViewById(R.id.data_port);
+ mDataPortView.setMovementMethod(new ScrollingMovementMethod());
+
+ mClearLogButton = (Button) rootView.findViewById(R.id.clear);
+ mClearLogButton.setOnClickListener(this);
+
+ setButtonsState(false);
+
+ return rootView;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view == mSendSingleConversation) {
+ sendMsg(1, 1);
+ } else if (view == mSendTwoConversations) {
+ sendMsg(2, 1);
+ } else if (view == mSendConversationWithThreeMessages) {
+ sendMsg(1, 3);
+ } else if (view == mClearLogButton) {
+ MessageLogger.clear(getActivity());
+ mDataPortView.setText(MessageLogger.getAllMessages(getActivity()));
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ getActivity().bindService(new Intent(getActivity(), MessagingService.class), mConnection,
+ Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ MessageLogger.getPrefs(getActivity()).unregisterOnSharedPreferenceChangeListener(listener);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mDataPortView.setText(MessageLogger.getAllMessages(getActivity()));
+ MessageLogger.getPrefs(getActivity()).registerOnSharedPreferenceChangeListener(listener);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mBound) {
+ getActivity().unbindService(mConnection);
+ mBound = false;
+ }
+ }
+
+ private void sendMsg(int howManyConversations, int messagesPerConversation) {
+ if (mBound) {
+ Message msg = Message.obtain(null, MessagingService.MSG_SEND_NOTIFICATION,
+ howManyConversations, messagesPerConversation);
+ try {
+ mService.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending a message", e);
+ MessageLogger.logMessage(getActivity(), "Error occurred while sending a message.");
+ }
+ }
+ }
+
+ private void setButtonsState(boolean enable) {
+ mSendSingleConversation.setEnabled(enable);
+ mSendTwoConversations.setEnabled(enable);
+ mSendConversationWithThreeMessages.setEnabled(enable);
+ }
+}
diff --git a/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessagingService.java b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessagingService.java
new file mode 100644
index 0000000..f980375
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/java/com/example/android/messagingservice/MessagingService.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.messagingservice;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.preview.support.v4.app.NotificationCompat.CarExtender;
+import android.preview.support.v4.app.NotificationCompat.CarExtender.UnreadConversation;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.app.RemoteInput;
+import android.util.Log;
+
+import java.util.Iterator;
+
+public class MessagingService extends Service {
+ private static final String TAG = MessagingService.class.getSimpleName();
+
+ public static final String READ_ACTION =
+ "com.example.android.messagingservice.ACTION_MESSAGE_READ";
+ public static final String REPLY_ACTION =
+ "com.example.android.messagingservice.ACTION_MESSAGE_REPLY";
+ public static final String CONVERSATION_ID = "conversation_id";
+ public static final String EXTRA_VOICE_REPLY = "extra_voice_reply";
+ public static final int MSG_SEND_NOTIFICATION = 1;
+ public static final String EOL = "\n";
+
+ private NotificationManagerCompat mNotificationManager;
+
+ private final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+ /**
+ * Handler of incoming messages from clients.
+ */
+ class IncomingHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SEND_NOTIFICATION:
+ int howManyConversations = msg.arg1 <= 0 ? 1 : msg.arg1;
+ int messagesPerConv = msg.arg2 <= 0 ? 1 : msg.arg2;
+ sendNotification(howManyConversations, messagesPerConv);
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ Log.d(TAG, "onCreate");
+ mNotificationManager = NotificationManagerCompat.from(getApplicationContext());
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.d(TAG, "onBind");
+ return mMessenger.getBinder();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG, "onStartCommand");
+ return START_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(TAG, "onDestroy");
+ }
+
+ // Creates an intent that will be triggered when a message is marked as read.
+ private Intent getMessageReadIntent(int id) {
+ return new Intent()
+ .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
+ .setAction(READ_ACTION)
+ .putExtra(CONVERSATION_ID, id);
+ }
+
+ // Creates an Intent that will be triggered when a voice reply is received.
+ private Intent getMessageReplyIntent(int conversationId) {
+ return new Intent()
+ .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
+ .setAction(REPLY_ACTION)
+ .putExtra(CONVERSATION_ID, conversationId);
+ }
+
+ private void sendNotification(int howManyConversations, int messagesPerConversation) {
+ Conversations.Conversation[] conversations = Conversations.getUnreadConversations(
+ howManyConversations, messagesPerConversation);
+ for (Conversations.Conversation conv : conversations) {
+ sendNotificationForConversation(conv);
+ }
+ }
+
+ private void sendNotificationForConversation(Conversations.Conversation conversation) {
+ // A pending Intent for reads
+ PendingIntent readPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
+ conversation.getConversationId(),
+ getMessageReadIntent(conversation.getConversationId()),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ // Build a RemoteInput for receiving voice input in a Car Notification
+ RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
+ .setLabel(getApplicationContext().getString(R.string.notification_reply))
+ .build();
+
+ // Building a Pending Intent for the reply action to trigger
+ PendingIntent replyIntent = PendingIntent.getBroadcast(getApplicationContext(),
+ conversation.getConversationId(),
+ getMessageReplyIntent(conversation.getConversationId()),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ // Create the UnreadConversation and populate it with the participant name,
+ // read and reply intents.
+ UnreadConversation.Builder unreadConvBuilder =
+ new UnreadConversation.Builder(conversation.getParticipantName())
+ .setLatestTimestamp(conversation.getTimestamp())
+ .setReadPendingIntent(readPendingIntent)
+ .setReplyAction(replyIntent, remoteInput);
+
+ // Note: Add messages from oldest to newest to the UnreadConversation.Builder
+ StringBuilder messageForNotification = new StringBuilder();
+ for (Iterator<String> messages = conversation.getMessages().iterator();
+ messages.hasNext(); ) {
+ String message = messages.next();
+ unreadConvBuilder.addMessage(message);
+ messageForNotification.append(message);
+ if (messages.hasNext()) {
+ messageForNotification.append(EOL);
+ }
+ }
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext())
+ .setSmallIcon(R.drawable.notification_icon)
+ .setLargeIcon(BitmapFactory.decodeResource(
+ getApplicationContext().getResources(), R.drawable.android_contact))
+ .setContentText(messageForNotification.toString())
+ .setWhen(conversation.getTimestamp())
+ .setContentTitle(conversation.getParticipantName())
+ .setContentIntent(readPendingIntent)
+ .extend(new CarExtender()
+ .setUnreadConversation(unreadConvBuilder.build())
+ .setColor(getApplicationContext()
+ .getResources().getColor(R.color.default_color_light)));
+
+ MessageLogger.logMessage(getApplicationContext(), "Sending notification "
+ + conversation.getConversationId() + " conversation: " + conversation);
+
+ mNotificationManager.notify(conversation.getConversationId(), builder.build());
+ }
+}
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_contact_picture.png b/notification/MessagingService/Application/src/main/res/drawable-hdpi/android_contact.png
similarity index 100%
copy from ui/notifications/LNotifications/Application/src/main/res/drawable-hdpi/ic_contact_picture.png
copy to notification/MessagingService/Application/src/main/res/drawable-hdpi/android_contact.png
Binary files differ
diff --git a/notification/MessagingService/Application/src/main/res/drawable-hdpi/ic_launcher.png b/notification/MessagingService/Application/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..06d85f1
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/notification/MessagingService/Application/src/main/res/drawable-hdpi/notification_icon.png b/notification/MessagingService/Application/src/main/res/drawable-hdpi/notification_icon.png
new file mode 100644
index 0000000..9cdfca1
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/drawable-hdpi/notification_icon.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_contact_picture.png b/notification/MessagingService/Application/src/main/res/drawable-mdpi/android_contact.png
similarity index 100%
copy from ui/notifications/LNotifications/Application/src/main/res/drawable-mdpi/ic_contact_picture.png
copy to notification/MessagingService/Application/src/main/res/drawable-mdpi/android_contact.png
Binary files differ
diff --git a/notification/MessagingService/Application/src/main/res/drawable-mdpi/ic_launcher.png b/notification/MessagingService/Application/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..4e1cc86
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/notification/MessagingService/Application/src/main/res/drawable-mdpi/notification_icon.png b/notification/MessagingService/Application/src/main/res/drawable-mdpi/notification_icon.png
new file mode 100644
index 0000000..d6069eb
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/drawable-mdpi/notification_icon.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_contact_picture.png b/notification/MessagingService/Application/src/main/res/drawable-xhdpi/android_contact.png
similarity index 100%
copy from ui/notifications/LNotifications/Application/src/main/res/drawable-xhdpi/ic_contact_picture.png
copy to notification/MessagingService/Application/src/main/res/drawable-xhdpi/android_contact.png
Binary files differ
diff --git a/notification/MessagingService/Application/src/main/res/drawable-xhdpi/ic_launcher.png b/notification/MessagingService/Application/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..92f1e2d
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/notification/MessagingService/Application/src/main/res/drawable-xhdpi/notification_icon.png b/notification/MessagingService/Application/src/main/res/drawable-xhdpi/notification_icon.png
new file mode 100644
index 0000000..786ed17
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/drawable-xhdpi/notification_icon.png
Binary files differ
diff --git a/ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_contact_picture.png b/notification/MessagingService/Application/src/main/res/drawable-xxhdpi/android_contact.png
similarity index 100%
copy from ui/notifications/LNotifications/Application/src/main/res/drawable-xxhdpi/ic_contact_picture.png
copy to notification/MessagingService/Application/src/main/res/drawable-xxhdpi/android_contact.png
Binary files differ
diff --git a/notification/MessagingService/Application/src/main/res/drawable-xxhdpi/ic_launcher.png b/notification/MessagingService/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2476cbd
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/notification/MessagingService/Application/src/main/res/drawable-xxhdpi/notification_icon.png b/notification/MessagingService/Application/src/main/res/drawable-xxhdpi/notification_icon.png
new file mode 100644
index 0000000..005207c
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/drawable-xxhdpi/notification_icon.png
Binary files differ
diff --git a/notification/MessagingService/Application/src/main/res/layout-land/fragment_message_me.xml b/notification/MessagingService/Application/src/main/res/layout-land/fragment_message_me.xml
new file mode 100644
index 0000000..8f7b60a
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/layout-land/fragment_message_me.xml
@@ -0,0 +1,67 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:orientation="horizontal"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin">
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+ <Button
+ android:id="@+id/send_1_conversation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/send_1_conversation"/>
+
+ <Button
+ android:id="@+id/send_2_conversations"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/send_2_conversations"/>
+
+ <Button
+ android:id="@+id/send_1_conversation_3_messages"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/send_1_conv_3_messages"/>
+ </LinearLayout>
+ <RelativeLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="2">
+ <Button
+ android:id="@+id/clear"
+ android:layout_alignParentBottom="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/clear_log"/>
+
+ <TextView
+ android:id="@+id/data_port"
+ android:layout_above="@id/clear"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:maxLines="20"
+ android:scrollbars="vertical"/>
+ </RelativeLayout>
+</LinearLayout>
diff --git a/notification/MessagingService/Application/src/main/res/layout/activity_main.xml b/notification/MessagingService/Application/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..59eec80
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/layout/activity_main.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".MainActivity"
+ tools:ignore="MergeRootFrame" />
diff --git a/notification/MessagingService/Application/src/main/res/layout/fragment_message_me.xml b/notification/MessagingService/Application/src/main/res/layout/fragment_message_me.xml
new file mode 100644
index 0000000..d01c513
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/layout/fragment_message_me.xml
@@ -0,0 +1,58 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<LinearLayout 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:orientation="vertical"
+ android:gravity="center_horizontal"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin">
+
+ <Button
+ android:id="@+id/send_1_conversation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/send_1_conversation"/>
+
+ <Button
+ android:id="@+id/send_2_conversations"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/send_2_conversations"/>
+
+ <Button
+ android:id="@+id/send_1_conversation_3_messages"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/send_1_conv_3_messages"/>
+
+ <TextView
+ android:id="@+id/data_port"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:maxLines="20"
+ android:scrollbars="vertical"/>
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/clear"
+ android:text="@string/clear_log"/>
+
+</LinearLayout>
diff --git a/notification/MessagingService/Application/src/main/res/values-v21/styles.xml b/notification/MessagingService/Application/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..f30c97a
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/values-v21/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+ <style name="AppTheme" parent="android:Theme.Material.Light">
+ <item name="android:colorPrimary">@color/default_color_light</item>
+ <item name="android:colorPrimaryDark">@color/default_color_dark</item>
+ </style>
+</resources>
diff --git a/notification/MessagingService/Application/src/main/res/values/colors.xml b/notification/MessagingService/Application/src/main/res/values/colors.xml
new file mode 100644
index 0000000..0e6825b
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+ <color name="default_color_light">#ff4092c3</color>
+ <color name="default_color_dark">#ff241c99</color>
+</resources>
diff --git a/notification/MessagingService/Application/src/main/res/values/dimens.xml b/notification/MessagingService/Application/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..574a35d
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/notification/MessagingService/Application/src/main/res/values/strings.xml b/notification/MessagingService/Application/src/main/res/values/strings.xml
new file mode 100644
index 0000000..001b10e
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/values/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+ <string name="app_name">Messaging Sample</string>
+ <string name="action_settings">Settings</string>
+ <string name="title">Messaging Sample</string>
+ <string name="notification_reply">Reply by Voice</string>
+ <string name="send_2_conversations">Send 2 conversations with 1 message</string>
+ <string name="send_1_conversation">Send 1 conversation with 1 message</string>
+ <string name="send_1_conv_3_messages">Send 1 conversation with 3 messages</string>
+ <string name="clear_log">Clear Log</string>
+</resources>
diff --git a/notification/MessagingService/Application/src/main/res/values/styles.xml b/notification/MessagingService/Application/src/main/res/values/styles.xml
new file mode 100644
index 0000000..3f1a6af
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/values/styles.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+ <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+ </style>
+</resources>
diff --git a/notification/MessagingService/Application/src/main/res/xml/automotive_app_desc.xml b/notification/MessagingService/Application/src/main/res/xml/automotive_app_desc.xml
new file mode 100644
index 0000000..9e9f174
--- /dev/null
+++ b/notification/MessagingService/Application/src/main/res/xml/automotive_app_desc.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<automotiveApp>
+ <uses name="notification"/>
+</automotiveApp>
diff --git a/ui/notifications/BasicNotifications/CONTRIB.md b/notification/MessagingService/CONTRIB.md
similarity index 100%
copy from ui/notifications/BasicNotifications/CONTRIB.md
copy to notification/MessagingService/CONTRIB.md
diff --git a/ui/notifications/BasicNotifications/LICENSE b/notification/MessagingService/LICENSE
similarity index 100%
copy from ui/notifications/BasicNotifications/LICENSE
copy to notification/MessagingService/LICENSE
diff --git a/notification/MessagingService/README.md b/notification/MessagingService/README.md
new file mode 100644
index 0000000..4cb5bb8
--- /dev/null
+++ b/notification/MessagingService/README.md
@@ -0,0 +1,88 @@
+Android MessagingService Sample
+==============================
+
+This sample shows a simple service that sends notifications using
+NotificationCompat. In addition to sending a notification, it also extends
+the notification with a CarExtender to make it compatible with Android Auto.
+Each unread conversation from a user is sent as a distinct notification.
+
+CheckList while building a messaging app that supports Android Auto:
+-------------------------------------------------------------------
+1. Add or import the android-auto-sdk.jar into your app.
+2. Ensure that Message notifications are extended using
+NotificationCompat.Builder.extend(new CarExtender()...)
+3. Add meta-data to your AndroidManifest.xml to specify that your app
+is automotive enabled.
+```
+ <meta-data android:name="com.google.android.gms.car.application"
+ android:resource="@xml/automotive_app_desc"/>
+```
+and include the following to indicate that the application wants to show notifications on
+the Android Auto overview screen.
+res/xml/automotive\_app\_desc.xml
+```
+<automotiveApp>
+ <uses name="notification"/>
+</automotiveApp>
+```
+
+Flow
+-----
+MessagingFragment is shown to the user. Depending on the button clicked, the MessagingService is
+sent a message. MessagingService inturn creates notifications which can be viewed either on the
+emulator or in a car.
+When a message is read, the associated PendingIntent is called and MessageReadReceiver is called
+with the appropriate conversationId. Similarly, when a reply is received, the MessageReplyReceiver
+is called with the appropriate conversationId. MessageLogger logs each event and shows them in a
+TextView in MessagingFragment for correlation.
+
+
+Pre-requisites
+--------------
+
+- Android SDK v21
+- Android Support Repository
+
+Getting Started
+---------------
+
+This sample uses the Gradle build system. To build this project, use the
+"gradlew build" command or use "Import Project" in Android Studio.
+
+Screenshots
+-----------
+
+<!-- Update these to point to screenshots. Add more as needed. -->
+![Description](screenshots/image1.png) ![Description](screenshots/image2.png)
+
+Support
+-------
+
+- Google+ Community: https://plus.google.com/communities/105153134372062985968
+- Stack Overflow: http://stackoverflow.com/questions/tagged/android
+
+If you've found an error in this sample, please file an issue:
+https://github.com/googlesamples/android-MessagingService
+
+Patches are encouraged, and may be submitted by forking this project and
+submitting a pull request through GitHub. Please see CONTRIBUTING.md for more details.
+
+License
+-------
+
+Copyright 2014 The Android Open Source Project, Inc.
+
+Licensed to the Apache Software Foundation (ASF) under one or more contributor
+license agreements. See the NOTICE file distributed with this work for
+additional information regarding copyright ownership. The ASF licenses this
+file to you under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
diff --git a/notification/MessagingService/build.gradle b/notification/MessagingService/build.gradle
new file mode 100644
index 0000000..2b8d1ef
--- /dev/null
+++ b/notification/MessagingService/build.gradle
@@ -0,0 +1,11 @@
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../build"
+ pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/ui/notifications/BasicNotifications/buildSrc/build.gradle b/notification/MessagingService/buildSrc/build.gradle
similarity index 63%
copy from ui/notifications/BasicNotifications/buildSrc/build.gradle
copy to notification/MessagingService/buildSrc/build.gradle
index e344a8c..8c294c2 100644
--- a/ui/notifications/BasicNotifications/buildSrc/build.gradle
+++ b/notification/MessagingService/buildSrc/build.gradle
@@ -1,6 +1,3 @@
-
-
-
repositories {
mavenCentral()
}
@@ -11,7 +8,7 @@
sourceSets {
main {
groovy {
- srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
+ srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
}
}
}
diff --git a/ui/notifications/LNotifications/gradle/wrapper/gradle-wrapper.jar b/notification/MessagingService/gradle/wrapper/gradle-wrapper.jar
similarity index 100%
copy from ui/notifications/LNotifications/gradle/wrapper/gradle-wrapper.jar
copy to notification/MessagingService/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/ui/notifications/BasicNotifications/gradle/wrapper/gradle-wrapper.properties b/notification/MessagingService/gradle/wrapper/gradle-wrapper.properties
similarity index 100%
copy from ui/notifications/BasicNotifications/gradle/wrapper/gradle-wrapper.properties
copy to notification/MessagingService/gradle/wrapper/gradle-wrapper.properties
diff --git a/ui/notifications/BasicNotifications/gradlew b/notification/MessagingService/gradlew
similarity index 100%
copy from ui/notifications/BasicNotifications/gradlew
copy to notification/MessagingService/gradlew
diff --git a/ui/notifications/BasicNotifications/gradlew.bat b/notification/MessagingService/gradlew.bat
similarity index 100%
copy from ui/notifications/BasicNotifications/gradlew.bat
copy to notification/MessagingService/gradlew.bat
diff --git a/notification/MessagingService/packaging.yaml b/notification/MessagingService/packaging.yaml
new file mode 100644
index 0000000..38733c7
--- /dev/null
+++ b/notification/MessagingService/packaging.yaml
@@ -0,0 +1,15 @@
+# GOOGLE SAMPLE PACKAGING DATA
+#
+# This file is used by Google as part of our samples packaging process.
+# End users may safely ignore this file. It has no relevance to other systems.
+---
+
+status: PUBLISHED
+technologies: [Android]
+categories: [None]
+languages: [Java]
+solutions: [Mobile]
+github: googlesamples/android-MessagingService
+level: BEGINNER
+icon: MessagingServiceSample/src/main/res/drawable-xxhdpi/ic_launcher.png
+license: apache2-android
diff --git a/ui/notifications/BasicNotifications/settings.gradle b/notification/MessagingService/settings.gradle
similarity index 100%
copy from ui/notifications/BasicNotifications/settings.gradle
copy to notification/MessagingService/settings.gradle
diff --git a/notification/MessagingService/template-params.xml b/notification/MessagingService/template-params.xml
new file mode 100644
index 0000000..edbeca9
--- /dev/null
+++ b/notification/MessagingService/template-params.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<sample>
+ <name>MessagingService</name>
+ <group>Notification</group>
+ <package>com.example.android.messagingservice</package>
+
+ <minSdk>21</minSdk>
+
+ <dependency_external>'libs/android-auto-sdk.jar'</dependency_external>
+
+ <strings>
+ <intro>
+ <![CDATA[
+This sample shows a simple service that sends notifications using
+NotificationCompat. In addition to sending a notification, it also extends
+the notification with a CarExtender to make it compatible with Android Auto.
+Each unread conversation from a user is sent as a distinct notification.
+ ]]>
+ </intro>
+ </strings>
+
+ <template src="basebuild" />
+</sample>
diff --git a/ui/notifications/BasicNotifications/build.gradle b/ui/notifications/BasicNotifications/build.gradle
deleted file mode 100644
index cca9ac3..0000000
--- a/ui/notifications/BasicNotifications/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-// BEGIN_EXCLUDE
-import com.example.android.samples.build.SampleGenPlugin
-apply plugin: SampleGenPlugin
-
-samplegen {
- pathToBuild "../../../../../build"
- pathToSamplesCommon "../../../common"
-}
-apply from: "../../../../../build/build.gradle"
-// END_EXCLUDE
diff --git a/ui/notifications/CustomNotifications/build.gradle b/ui/notifications/CustomNotifications/build.gradle
deleted file mode 100644
index cca9ac3..0000000
--- a/ui/notifications/CustomNotifications/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-// BEGIN_EXCLUDE
-import com.example.android.samples.build.SampleGenPlugin
-apply plugin: SampleGenPlugin
-
-samplegen {
- pathToBuild "../../../../../build"
- pathToSamplesCommon "../../../common"
-}
-apply from: "../../../../../build/build.gradle"
-// END_EXCLUDE
diff --git a/ui/notifications/CustomNotifications/buildSrc/build.gradle b/ui/notifications/CustomNotifications/buildSrc/build.gradle
deleted file mode 100644
index e344a8c..0000000
--- a/ui/notifications/CustomNotifications/buildSrc/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-repositories {
- mavenCentral()
-}
-dependencies {
- compile 'org.freemarker:freemarker:2.3.20'
-}
-
-sourceSets {
- main {
- groovy {
- srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
- }
- }
-}
-
diff --git a/ui/notifications/LNotifications/CONTRIB.md b/ui/notifications/LNotifications/CONTRIB.md
deleted file mode 100644
index 8ddb52d..0000000
--- a/ui/notifications/LNotifications/CONTRIB.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# How to become a contributor and submit your own code
-
-## Contributor License Agreements
-
-We'd love to accept your sample apps and patches! Before we can take them, we
-have to jump a couple of legal hurdles.
-
-Please fill out either the individual or corporate Contributor License Agreement (CLA).
-
- * If you are an individual writing original source code and you're sure you
- own the intellectual property, then you'll need to sign an [individual CLA]
- (https://developers.google.com/open-source/cla/individual).
- * If you work for a company that wants to allow you to contribute your work,
- then you'll need to sign a [corporate CLA]
- (https://developers.google.com/open-source/cla/corporate).
-
-Follow either of the two links above to access the appropriate CLA and
-instructions for how to sign and return it. Once we receive it, we'll be able to
-accept your pull requests.
-
-## Contributing A Patch
-
-1. Submit an issue describing your proposed change to the repo in question.
-1. The repo owner will respond to your issue promptly.
-1. If your proposed change is accepted, and you haven't already done so, sign a
- Contributor License Agreement (see details above).
-1. Fork the desired repo, develop and test your code changes.
-1. Ensure that your code adheres to the existing style in the sample to which
- you are contributing. Refer to the
- [Google Cloud Platform Samples Style Guide]
- (https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the
- recommended coding standards for this organization.
-1. Ensure that your code has an appropriate set of unit tests which all pass.
-1. Submit a pull request.
-
diff --git a/ui/notifications/LNotifications/LICENSE b/ui/notifications/LNotifications/LICENSE
deleted file mode 100644
index c02ca2f..0000000
--- a/ui/notifications/LNotifications/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright 2014 Google Inc.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/ui/notifications/LNotifications/build.gradle b/ui/notifications/LNotifications/build.gradle
deleted file mode 100644
index be1fa82..0000000
--- a/ui/notifications/LNotifications/build.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-// BEGIN_EXCLUDE
-import com.example.android.samples.build.SampleGenPlugin
-apply plugin: SampleGenPlugin
-
-samplegen {
- pathToBuild "../../../../../build"
- pathToSamplesCommon "../../../common"
-}
-apply from: "../../../../../build/build.gradle"
-// END_EXCLUDE
diff --git a/ui/notifications/LNotifications/buildSrc/build.gradle b/ui/notifications/LNotifications/buildSrc/build.gradle
deleted file mode 100644
index e344a8c..0000000
--- a/ui/notifications/LNotifications/buildSrc/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-repositories {
- mavenCentral()
-}
-dependencies {
- compile 'org.freemarker:freemarker:2.3.20'
-}
-
-sourceSets {
- main {
- groovy {
- srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
- }
- }
-}
-