blob: 327c4b86c40df3b716e2fd98a7c04b5c618489d3 [file] [log] [blame]
/*
* 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.wearable.quiz;
import static com.example.android.wearable.quiz.Constants.ANSWERS;
import static com.example.android.wearable.quiz.Constants.CONNECT_TIMEOUT_MS;
import static com.example.android.wearable.quiz.Constants.CORRECT_ANSWER_INDEX;
import static com.example.android.wearable.quiz.Constants.NUM_CORRECT;
import static com.example.android.wearable.quiz.Constants.NUM_INCORRECT;
import static com.example.android.wearable.quiz.Constants.NUM_SKIPPED;
import static com.example.android.wearable.quiz.Constants.QUESTION;
import static com.example.android.wearable.quiz.Constants.QUESTION_INDEX;
import static com.example.android.wearable.quiz.Constants.QUESTION_WAS_ANSWERED;
import static com.example.android.wearable.quiz.Constants.QUESTION_WAS_DELETED;
import static com.example.android.wearable.quiz.Constants.QUIZ_ENDED_PATH;
import static com.example.android.wearable.quiz.Constants.QUIZ_EXITED_PATH;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.data.FreezableUtils;
import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.DataItem;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.WearableListenerService;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Listens to changes in DataItems, which represent quiz questions.
* If a new question is created, this builds a new notification for it.
* Otherwise, if a question is deleted, this cancels the corresponding notification.
*
* When the quiz ends, this listener receives a message telling it to create an end-of-quiz report.
*/
public class QuizListenerService extends WearableListenerService {
private static final String TAG = "QuizSample";
private static final int QUIZ_REPORT_NOTIF_ID = -1; // Never used by question notifications.
private static final Map<Integer, Integer> questionNumToDrawableId;
static {
Map<Integer, Integer> temp = new HashMap<Integer, Integer>(4);
temp.put(0, R.drawable.ic_choice_a);
temp.put(1, R.drawable.ic_choice_b);
temp.put(2, R.drawable.ic_choice_c);
temp.put(3, R.drawable.ic_choice_d);
questionNumToDrawableId = Collections.unmodifiableMap(temp);
}
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.build();
ConnectionResult connectionResult = googleApiClient.blockingConnect(CONNECT_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
if (!connectionResult.isSuccess()) {
Log.e(TAG, "QuizListenerService failed to connect to GoogleApiClient.");
return;
}
for (DataEvent event : events) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
DataItem dataItem = event.getDataItem();
DataMap dataMap = DataMapItem.fromDataItem(dataItem).getDataMap();
if (dataMap.getBoolean(QUESTION_WAS_ANSWERED)
|| dataMap.getBoolean(QUESTION_WAS_DELETED)) {
// Ignore the change in data; it is used in MainActivity to update
// the question's status (i.e. was the answer right or wrong or left blank).
continue;
}
String question = dataMap.getString(QUESTION);
int questionIndex = dataMap.getInt(QUESTION_INDEX);
int questionNum = questionIndex + 1;
String[] answers = dataMap.getStringArray(ANSWERS);
int correctAnswerIndex = dataMap.getInt(CORRECT_ANSWER_INDEX);
Intent deleteOperation = new Intent(this, DeleteQuestionService.class);
deleteOperation.setData(dataItem.getUri());
PendingIntent deleteIntent = PendingIntent.getService(this, 0,
deleteOperation, PendingIntent.FLAG_UPDATE_CURRENT);
// First page of notification contains question as Big Text.
Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle()
.setBigContentTitle(getString(R.string.question, questionNum))
.bigText(question);
Notification.Builder builder = new Notification.Builder(this)
.setStyle(bigTextStyle)
.setSmallIcon(R.drawable.ic_launcher)
.setLocalOnly(true)
.setDeleteIntent(deleteIntent);
// Add answers as actions.
Notification.WearableExtender wearableOptions = new Notification.WearableExtender();
for (int i = 0; i < answers.length; i++) {
Notification answerPage = new Notification.Builder(this)
.setContentTitle(question)
.setContentText(answers[i])
.extend(new Notification.WearableExtender()
.setContentAction(i))
.build();
boolean correct = (i == correctAnswerIndex);
Intent updateOperation = new Intent(this, UpdateQuestionService.class);
// Give each intent a unique action.
updateOperation.setAction("question_" + questionIndex + "_answer_" + i);
updateOperation.setData(dataItem.getUri());
updateOperation.putExtra(UpdateQuestionService.EXTRA_QUESTION_INDEX,
questionIndex);
updateOperation.putExtra(UpdateQuestionService.EXTRA_QUESTION_CORRECT, correct);
PendingIntent updateIntent = PendingIntent.getService(this, 0, updateOperation,
PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Action action = new Notification.Action.Builder(
questionNumToDrawableId.get(i), null, updateIntent)
.build();
wearableOptions.addAction(action).addPage(answerPage);
}
builder.extend(wearableOptions);
Notification notification = builder.build();
((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
.notify(questionIndex, notification);
} else if (event.getType() == DataEvent.TYPE_DELETED) {
Uri uri = event.getDataItem().getUri();
// URI's are of the form "/question/0", "/question/1" etc.
// We use the question index as the notification id.
int notificationId = Integer.parseInt(uri.getLastPathSegment());
((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
.cancel(notificationId);
}
// Delete the quiz report, if it exists.
((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
.cancel(QUIZ_REPORT_NOTIF_ID);
}
googleApiClient.disconnect();
}
@Override
public void onMessageReceived(MessageEvent messageEvent) {
String path = messageEvent.getPath();
if (path.equals(QUIZ_EXITED_PATH)) {
// Remove any lingering question notifications.
((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancelAll();
}
if (path.equals(QUIZ_ENDED_PATH) || path.equals(QUIZ_EXITED_PATH)) {
// Quiz ended - display overall results.
DataMap dataMap = DataMap.fromByteArray(messageEvent.getData());
int numCorrect = dataMap.getInt(NUM_CORRECT);
int numIncorrect = dataMap.getInt(NUM_INCORRECT);
int numSkipped = dataMap.getInt(NUM_SKIPPED);
Notification.Builder builder = new Notification.Builder(this)
.setContentTitle(getString(R.string.quiz_report))
.setSmallIcon(R.drawable.ic_launcher)
.setLocalOnly(true);
SpannableStringBuilder quizReportText = new SpannableStringBuilder();
appendColored(quizReportText, String.valueOf(numCorrect), R.color.dark_green);
quizReportText.append(" " + getString(R.string.correct) + "\n");
appendColored(quizReportText, String.valueOf(numIncorrect), R.color.dark_red);
quizReportText.append(" " + getString(R.string.incorrect) + "\n");
appendColored(quizReportText, String.valueOf(numSkipped), R.color.dark_yellow);
quizReportText.append(" " + getString(R.string.skipped) + "\n");
builder.setContentText(quizReportText);
if (!path.equals(QUIZ_EXITED_PATH)) {
// Don't add reset option if user exited quiz (there might not be a quiz to reset!).
builder.addAction(R.drawable.ic_launcher,
getString(R.string.reset_quiz), getResetQuizPendingIntent());
}
((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
.notify(QUIZ_REPORT_NOTIF_ID, builder.build());
}
}
private void appendColored(SpannableStringBuilder builder, String text, int colorResId) {
builder.append(text).setSpan(new ForegroundColorSpan(getResources().getColor(colorResId)),
builder.length() - text.length(), builder.length(), 0);
}
/**
* Returns a PendingIntent that will send a message to the phone to reset the quiz when fired.
*/
private PendingIntent getResetQuizPendingIntent() {
Intent intent = new Intent(QuizReportActionService.ACTION_RESET_QUIZ)
.setClass(this, QuizReportActionService.class);
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}