Home | History | Annotate | Download | only in com.example.android.wearable.quiz
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.wearable.quiz;
     18 
     19 import static com.example.android.wearable.quiz.Constants.ANSWERS;
     20 import static com.example.android.wearable.quiz.Constants.CONNECT_TIMEOUT_MS;
     21 import static com.example.android.wearable.quiz.Constants.CORRECT_ANSWER_INDEX;
     22 import static com.example.android.wearable.quiz.Constants.NUM_CORRECT;
     23 import static com.example.android.wearable.quiz.Constants.NUM_INCORRECT;
     24 import static com.example.android.wearable.quiz.Constants.NUM_SKIPPED;
     25 import static com.example.android.wearable.quiz.Constants.QUESTION;
     26 import static com.example.android.wearable.quiz.Constants.QUESTION_INDEX;
     27 import static com.example.android.wearable.quiz.Constants.QUESTION_WAS_ANSWERED;
     28 import static com.example.android.wearable.quiz.Constants.QUESTION_WAS_DELETED;
     29 import static com.example.android.wearable.quiz.Constants.QUIZ_ENDED_PATH;
     30 import static com.example.android.wearable.quiz.Constants.QUIZ_EXITED_PATH;
     31 
     32 import android.app.Notification;
     33 import android.app.NotificationManager;
     34 import android.app.PendingIntent;
     35 import android.content.Intent;
     36 import android.net.Uri;
     37 import android.text.SpannableStringBuilder;
     38 import android.text.style.ForegroundColorSpan;
     39 import android.util.Log;
     40 
     41 import com.google.android.gms.common.ConnectionResult;
     42 import com.google.android.gms.common.api.GoogleApiClient;
     43 import com.google.android.gms.wearable.DataEvent;
     44 import com.google.android.gms.wearable.DataEventBuffer;
     45 import com.google.android.gms.wearable.DataItem;
     46 import com.google.android.gms.wearable.DataMap;
     47 import com.google.android.gms.wearable.DataMapItem;
     48 import com.google.android.gms.wearable.MessageEvent;
     49 import com.google.android.gms.wearable.Wearable;
     50 import com.google.android.gms.wearable.WearableListenerService;
     51 
     52 import java.util.Collections;
     53 import java.util.HashMap;
     54 import java.util.List;
     55 import java.util.Map;
     56 import java.util.concurrent.TimeUnit;
     57 
     58 /**
     59  * Listens to changes in DataItems, which represent quiz questions.
     60  * If a new question is created, this builds a new notification for it.
     61  * Otherwise, if a question is deleted, this cancels the corresponding notification.
     62  *
     63  * When the quiz ends, this listener receives a message telling it to create an end-of-quiz report.
     64  */
     65 public class QuizListenerService extends WearableListenerService {
     66 
     67     private static final String TAG = "QuizSample";
     68     private static final int QUIZ_REPORT_NOTIF_ID = -1; // Never used by question notifications.
     69     private static final Map<Integer, Integer> questionNumToDrawableId;
     70 
     71     static {
     72         Map<Integer, Integer> temp = new HashMap<Integer, Integer>(4);
     73         temp.put(0, R.drawable.ic_choice_a);
     74         temp.put(1, R.drawable.ic_choice_b);
     75         temp.put(2, R.drawable.ic_choice_c);
     76         temp.put(3, R.drawable.ic_choice_d);
     77         questionNumToDrawableId = Collections.unmodifiableMap(temp);
     78     }
     79 
     80     @Override
     81     public void onDataChanged(DataEventBuffer dataEvents) {
     82         GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
     83                 .addApi(Wearable.API)
     84                 .build();
     85 
     86         ConnectionResult connectionResult = googleApiClient.blockingConnect(CONNECT_TIMEOUT_MS,
     87                 TimeUnit.MILLISECONDS);
     88         if (!connectionResult.isSuccess()) {
     89             Log.e(TAG, "QuizListenerService failed to connect to GoogleApiClient.");
     90             return;
     91         }
     92 
     93         for (DataEvent event : dataEvents) {
     94             if (event.getType() == DataEvent.TYPE_CHANGED) {
     95                 DataItem dataItem = event.getDataItem();
     96                 DataMap dataMap = DataMapItem.fromDataItem(dataItem).getDataMap();
     97                 if (dataMap.getBoolean(QUESTION_WAS_ANSWERED)
     98                         || dataMap.getBoolean(QUESTION_WAS_DELETED)) {
     99                     // Ignore the change in data; it is used in MainActivity to update
    100                     // the question's status (i.e. was the answer right or wrong or left blank).
    101                     continue;
    102                 }
    103                 String question = dataMap.getString(QUESTION);
    104                 int questionIndex = dataMap.getInt(QUESTION_INDEX);
    105                 int questionNum = questionIndex + 1;
    106                 String[] answers = dataMap.getStringArray(ANSWERS);
    107                 int correctAnswerIndex = dataMap.getInt(CORRECT_ANSWER_INDEX);
    108                 Intent deleteOperation = new Intent(this, DeleteQuestionService.class);
    109                 deleteOperation.setData(dataItem.getUri());
    110                 PendingIntent deleteIntent = PendingIntent.getService(this, 0,
    111                         deleteOperation, PendingIntent.FLAG_UPDATE_CURRENT);
    112                 // First page of notification contains question as Big Text.
    113                 Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle()
    114                         .setBigContentTitle(getString(R.string.question, questionNum))
    115                         .bigText(question);
    116                 Notification.Builder builder = new Notification.Builder(this)
    117                         .setStyle(bigTextStyle)
    118                         .setSmallIcon(R.drawable.ic_launcher)
    119                         .setLocalOnly(true)
    120                         .setDeleteIntent(deleteIntent);
    121 
    122                 // Add answers as actions.
    123                 Notification.WearableExtender wearableOptions = new Notification.WearableExtender();
    124                 for (int i = 0; i < answers.length; i++) {
    125                     Notification answerPage = new Notification.Builder(this)
    126                             .setContentTitle(question)
    127                             .setContentText(answers[i])
    128                             .extend(new Notification.WearableExtender()
    129                                     .setContentAction(i))
    130                             .build();
    131 
    132                     boolean correct = (i == correctAnswerIndex);
    133                     Intent updateOperation = new Intent(this, UpdateQuestionService.class);
    134                     // Give each intent a unique action.
    135                     updateOperation.setAction("question_" + questionIndex + "_answer_" + i);
    136                     updateOperation.setData(dataItem.getUri());
    137                     updateOperation.putExtra(UpdateQuestionService.EXTRA_QUESTION_INDEX,
    138                             questionIndex);
    139                     updateOperation.putExtra(UpdateQuestionService.EXTRA_QUESTION_CORRECT, correct);
    140                     PendingIntent updateIntent = PendingIntent.getService(this, 0, updateOperation,
    141                             PendingIntent.FLAG_UPDATE_CURRENT);
    142                     Notification.Action action = new Notification.Action.Builder(
    143                             questionNumToDrawableId.get(i), null, updateIntent)
    144                             .build();
    145                     wearableOptions.addAction(action).addPage(answerPage);
    146                 }
    147                 builder.extend(wearableOptions);
    148                 Notification notification = builder.build();
    149                 ((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
    150                         .notify(questionIndex, notification);
    151             } else if (event.getType() == DataEvent.TYPE_DELETED) {
    152                 Uri uri = event.getDataItem().getUri();
    153                 // URI's are of the form "/question/0", "/question/1" etc.
    154                 // We use the question index as the notification id.
    155                 int notificationId = Integer.parseInt(uri.getLastPathSegment());
    156                 ((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
    157                         .cancel(notificationId);
    158             }
    159             // Delete the quiz report, if it exists.
    160             ((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
    161                     .cancel(QUIZ_REPORT_NOTIF_ID);
    162         }
    163         googleApiClient.disconnect();
    164     }
    165 
    166     @Override
    167     public void onMessageReceived(MessageEvent messageEvent) {
    168         String path = messageEvent.getPath();
    169         if (path.equals(QUIZ_EXITED_PATH)) {
    170             // Remove any lingering question notifications.
    171             ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancelAll();
    172         }
    173         if (path.equals(QUIZ_ENDED_PATH) || path.equals(QUIZ_EXITED_PATH)) {
    174             // Quiz ended - display overall results.
    175             DataMap dataMap = DataMap.fromByteArray(messageEvent.getData());
    176             int numCorrect = dataMap.getInt(NUM_CORRECT);
    177             int numIncorrect = dataMap.getInt(NUM_INCORRECT);
    178             int numSkipped = dataMap.getInt(NUM_SKIPPED);
    179 
    180             Notification.Builder builder = new Notification.Builder(this)
    181                     .setContentTitle(getString(R.string.quiz_report))
    182                     .setSmallIcon(R.drawable.ic_launcher)
    183                     .setLocalOnly(true);
    184             SpannableStringBuilder quizReportText = new SpannableStringBuilder();
    185             appendColored(quizReportText, String.valueOf(numCorrect), R.color.dark_green);
    186             quizReportText.append(" " + getString(R.string.correct) + "\n");
    187             appendColored(quizReportText, String.valueOf(numIncorrect), R.color.dark_red);
    188             quizReportText.append(" " + getString(R.string.incorrect) + "\n");
    189             appendColored(quizReportText, String.valueOf(numSkipped), R.color.dark_yellow);
    190             quizReportText.append(" " + getString(R.string.skipped) + "\n");
    191 
    192             builder.setContentText(quizReportText);
    193             if (!path.equals(QUIZ_EXITED_PATH)) {
    194                 // Don't add reset option if user exited quiz (there might not be a quiz to reset!).
    195                 builder.addAction(R.drawable.ic_launcher,
    196                         getString(R.string.reset_quiz), getResetQuizPendingIntent());
    197             }
    198             ((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
    199                     .notify(QUIZ_REPORT_NOTIF_ID, builder.build());
    200         }
    201     }
    202 
    203     private void appendColored(SpannableStringBuilder builder, String text, int colorResId) {
    204         builder.append(text).setSpan(new ForegroundColorSpan(getResources().getColor(colorResId)),
    205                 builder.length() - text.length(), builder.length(), 0);
    206     }
    207 
    208     /**
    209      * Returns a PendingIntent that will send a message to the phone to reset the quiz when fired.
    210      */
    211     private PendingIntent getResetQuizPendingIntent() {
    212         Intent intent = new Intent(QuizReportActionService.ACTION_RESET_QUIZ)
    213                 .setClass(this, QuizReportActionService.class);
    214         return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    215     }
    216 }
    217